| // 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; |
| } |