| // Copyright 2018 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | // Benchmarks the IO system on a device, by writing and then readind a file | 
 | // filled with random data. | 
 |  | 
 | #include <fcntl.h> | 
 | #include <string.h> | 
 | #include <sys/stat.h> | 
 | #include <sys/types.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include <atomic> | 
 | #include <random> | 
 | #include <string> | 
 | #include <thread> | 
 | #include <vector> | 
 |  | 
 | #include "base/files/file.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/logging.h" | 
 | #include "base/memory/aligned_memory.h" | 
 | #include "base/posix/eintr_wrapper.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/test/test_file_util.h" | 
 | #include "base/threading/platform_thread.h" | 
 | #include "base/time/time.h" | 
 | #include "build/build_config.h" | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr int kPageSize = 1 << 12; | 
 |  | 
 | std::mt19937 RandomEngine() { | 
 |   std::random_device r; | 
 |   std::seed_seq seed({r(), r(), r(), r(), r(), r(), r(), r()}); | 
 |   return std::mt19937(seed); | 
 | } | 
 |  | 
 | std::vector<uint8_t> RandomData(size_t size, std::mt19937* engine) { | 
 |   std::uniform_int_distribution<uint8_t> dist(0, 255); | 
 |   std::vector<uint8_t> data(size); | 
 |   for (size_t i = 0; i < size; ++i) | 
 |     data[i] = dist(*engine); | 
 |  | 
 |   return data; | 
 | } | 
 |  | 
 | std::string DurationLogMessage(const char* prefix, | 
 |                                const base::TimeTicks& tick, | 
 |                                const base::TimeTicks& tock, | 
 |                                int size) { | 
 |   double delta_us = (tock - tick).InMicrosecondsF(); | 
 |   double mb_per_second = (static_cast<double>(size) / 1e6) / (delta_us / 1e6); | 
 |   std::string message = base::StringPrintf("%s %d = %.0fus (%.02fMB/s)", prefix, | 
 |                                            size, delta_us, mb_per_second); | 
 |   return message; | 
 | } | 
 |  | 
 | // Returns {write_us, read_us}. | 
 | std::pair<int64_t, int64_t> WriteReadData(int size, | 
 |                                           const std::string& filename, | 
 |                                           bool drop_cache) { | 
 |   int64_t read_us, write_us; | 
 |  | 
 |   // Using random data for two reasons: | 
 |   // - Some filesystems do transparent compression. | 
 |   // - Some flash controllers do transparent compression | 
 |   // | 
 |   // To defeat it and get the actual IO throughput and latency, make the data | 
 |   // incompressible (which is also the case when writing compressed data). | 
 |   auto engine = RandomEngine(); | 
 |   std::vector<uint8_t> data = RandomData(size, &engine); | 
 |  | 
 |   auto path = base::FilePath(filename); | 
 |   // Write. | 
 |   { | 
 |     auto f = base::File( | 
 |         path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); | 
 |     CHECK(f.IsValid()); | 
 |  | 
 |     auto tick = base::TimeTicks::Now(); | 
 |     int written = | 
 |         f.WriteAtCurrentPos(reinterpret_cast<const char*>(&data[0]), size); | 
 |     CHECK_EQ(size, written); | 
 |     auto tock = base::TimeTicks::Now(); | 
 |  | 
 |     LOG(INFO) << DurationLogMessage("\tWrite", tick, tock, size); | 
 |     write_us = (tock - tick).InMicroseconds(); | 
 |  | 
 |     CHECK(f.Flush()); | 
 |   } | 
 |  | 
 |   if (drop_cache) { | 
 |     CHECK(base::EvictFileFromSystemCache(path)); | 
 |     // Sleeping, as posix_fadvise() is asynchronous. On the other hand, we | 
 |     // don't need to sleep for too long, as all the pages are already clean | 
 |     // after the fsync() above, so no writeback is required here. | 
 |     base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); | 
 |   } | 
 |  | 
 |   // Read. | 
 |   { | 
 |     auto f = base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ); | 
 |     CHECK(f.IsValid()); | 
 |  | 
 |     auto tick = base::TimeTicks::Now(); | 
 |     int read = f.ReadAtCurrentPos(reinterpret_cast<char*>(&data[0]), size); | 
 |     CHECK_EQ(size, read); | 
 |     auto tock = base::TimeTicks::Now(); | 
 |  | 
 |     LOG(INFO) << DurationLogMessage("\tRead", tick, tock, size); | 
 |     read_us = (tock - tick).InMicroseconds(); | 
 |   } | 
 |  | 
 |   CHECK(base::DeleteFile(path, false)); | 
 |   return {write_us, read_us}; | 
 | } | 
 |  | 
 | // Will constantly do 4k random IO to |filename| until |should_stop| is true. | 
 | void RandomlyReadWrite(std::atomic<bool>* should_stop, | 
 |                        const std::string& filename, | 
 |                        int i) { | 
 |   constexpr int kPages = 1 << 10; | 
 |   constexpr int kSize = kPages * kPageSize;  // 4MiB (2**10 4k Pages). | 
 |   auto path = base::FilePath(filename); | 
 |   auto engine = RandomEngine(); | 
 |   std::vector<uint8_t> data = RandomData(kSize, &engine); | 
 |  | 
 |   LOG(INFO) << "Noisy neighbor " << i << ": initial file write"; | 
 |   { | 
 |     auto f = base::File( | 
 |         path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); | 
 |     CHECK(f.IsValid()); | 
 |     int written = | 
 |         f.WriteAtCurrentPos(reinterpret_cast<const char*>(&data[0]), kSize); | 
 |     CHECK_EQ(kSize, written); | 
 |   } | 
 |  | 
 |   auto dist = std::uniform_int_distribution<int>(0, kPages - 1); | 
 |  | 
 |   LOG(INFO) << "Noisy neighbor " << i << ": Go"; | 
 |   { | 
 |     // Opening the file ourselves as base::File doesn't have flags for O_DIRECT. | 
 |     // | 
 |     // O_DIRECT is used to make sure that reads and writes are not cached, | 
 |     // and come straight from the storage device. | 
 |     int fd = open(filename.c_str(), O_RDWR | O_DIRECT | O_SYNC); | 
 |     CHECK_NE(fd, -1); | 
 |     auto f = base::File(fd); | 
 |     // O_DIRECT has special requirements on read/write buffers alignment, | 
 |     // which are unspecified in "man open(2)". However a page-aligned buffer | 
 |     // works with linux filesystems (512 bytes is usually enough). | 
 |     std::unique_ptr<char, base::AlignedFreeDeleter> page_buffer( | 
 |         static_cast<char*>(base::AlignedAlloc(kPageSize, kPageSize))); | 
 |  | 
 |     while (!should_stop->load()) { | 
 |       int i = dist(engine); | 
 |       int offset = i * kPageSize; | 
 |       int size_read = f.Read(offset, page_buffer.get(), kPageSize); | 
 |       CHECK_EQ(size_read, kPageSize); | 
 |  | 
 |       std::vector<uint8_t> random_page = RandomData(kPageSize, &engine); | 
 |       int written = | 
 |           f.Write(offset, reinterpret_cast<char*>(&random_page[0]), kPageSize); | 
 |       CHECK_EQ(written, kPageSize); | 
 |     } | 
 |   } | 
 |  | 
 |   LOG(INFO) << "Noisy neighbor " << i << ": Finishing"; | 
 |   base::DeleteFile(path, false); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | int main(int argc, char** argv) { | 
 |   if (argc != 4) { | 
 |     LOG(ERROR) << "\nUsage: " << argv[0] | 
 |                << "FILENAME DROP_CACHES NUM_NOISY_NEIGBORS\n\n" | 
 |                << "Where: FILENAME            path to the test file " | 
 |                << "(writable).\n" | 
 |                << "       DROP_CACHES         1 to drop the filesystem cache, " | 
 |                << "0 otherwise.\n" | 
 |                << "       NUM_NOISY_NEIGBORS  number of noisy neighbor threads " | 
 |                << "to start."; | 
 |     return 1; | 
 |   } | 
 |   char* filename = argv[1]; | 
 |   int drop_caches = atoi(argv[2]); | 
 |   int neighbors = atoi(argv[3]); | 
 |  | 
 |   std::atomic<bool> should_stop; | 
 |   should_stop.store(false); | 
 |   std::atomic<bool>* should_stop_ptr = &should_stop; | 
 |  | 
 |   std::vector<std::thread> noisy_neighbors; | 
 |   for (int i = 0; i < neighbors; ++i) { | 
 |     std::string path = base::StringPrintf("%s-noisy_neighbor-%d", filename, i); | 
 |     noisy_neighbors.emplace_back( | 
 |         [=]() { RandomlyReadWrite(should_stop_ptr, path, i); }); | 
 |     base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(2)); | 
 |   } | 
 |  | 
 |   for (int i = 0; i < 12; i++) {  // Max 1 << 11 pages = 8MiB. | 
 |     int size = (1 << i) * kPageSize; | 
 |     LOG(INFO) << "Size = " << size; | 
 |  | 
 |     auto write_read_us = | 
 |         WriteReadData(size, std::string(filename), drop_caches != 0); | 
 |     std::string csv_log = | 
 |         base::StringPrintf("%d,%d,%d,%d,%d", drop_caches, neighbors, size, | 
 |                            static_cast<int>(write_read_us.first), | 
 |                            static_cast<int>(write_read_us.second)); | 
 |     LOG(INFO) << "CSV: " << csv_log; | 
 |   } | 
 |  | 
 |   should_stop.store(true); | 
 |   for (int i = 0; i < neighbors; ++i) { | 
 |     noisy_neighbors[i].join(); | 
 |   } | 
 |  | 
 |   return 0; | 
 | } |