Commit 989545c9 authored by Benoit Lize's avatar Benoit Lize Committed by Commit Bot

tools/android: Disk IO simple benchmark tool.

Adds a simple tool writing random data to a file, then reading it. The
file is optionally evicted from the OS page cache between the read and
write. This is used to assess device IO performance locally.

From an Android Go device (Gobo) running O MR1 with 360MB free on /data:

Without the OS page cache:
$ adb shell /data/local/tmp/io_benchmark /data/local/tmp/test 1
Size = 4096
 Write 4096 = 50.0us (81.92MB/s)
 Read 4096 = 1489.0us (2.75MB/s)
Size = 8192
 Write 8192 = 61.0us (134.30MB/s)
 Read 8192 = 1486.0us (5.51MB/s)
Size = 16384
 Write 16384 = 81.0us (202.27MB/s)
 Read 16384 = 1602.0us (10.23MB/s)
Size = 32768
 Write 32768 = 145.0us (225.99MB/s)
 Read 32768 = 1877.0us (17.46MB/s)
Size = 65536
 Write 65536 = 215.0us (304.82MB/s)
 Read 65536 = 2656.0us (24.67MB/s)
Size = 131072
 Write 131072 = 468.0us (280.07MB/s)
 Read 131072 = 4531.0us (28.93MB/s)
Size = 262144
 Write 262144 = 642.0us (408.32MB/s)
 Read 262144 = 5041.0us (52.00MB/s)
Size = 524288
 Write 524288 = 1219.0us (430.10MB/s)
 Read 524288 = 8949.0us (58.59MB/s)
Size = 1048576
 Write 1048576 = 2466.0us (425.21MB/s)
 Read 1048576 = 13606.0us (77.07MB/s)
Size = 2097152
 Write 2097152 = 4908.0us (427.29MB/s)
 Read 2097152 = 25840.0us (81.16MB/s)
Size = 4194304
 Write 4194304 = 9502.0us (441.41MB/s)
 Read 4194304 = 76867.0us (54.57MB/s)
Size = 8388608
 Write 8388608 = 20814.0us (403.03MB/s)
 Read 8388608 = 119062.0us (70.46MB/s)

With it:
$ adb shell /data/local/tmp/io_benchmark /data/local/tmp/test 0
Size = 4096
 Write 4096 = 56.0us (73.14MB/s)
 Read 4096 = 172.0us (23.81MB/s)
Size = 8192
 Write 8192 = 65.0us (126.03MB/s)
 Read 8192 = 23.0us (356.17MB/s)
Size = 16384
 Write 16384 = 101.0us (162.22MB/s)
 Read 16384 = 35.0us (468.11MB/s)
Size = 32768
 Write 32768 = 134.0us (244.54MB/s)
 Read 32768 = 57.0us (574.88MB/s)
Size = 65536
 Write 65536 = 229.0us (286.18MB/s)
 Read 65536 = 109.0us (601.25MB/s)
Size = 131072
 Write 131072 = 348.0us (376.64MB/s)
 Read 131072 = 170.0us (771.01MB/s)
Size = 262144
 Write 262144 = 721.0us (363.58MB/s)
 Read 262144 = 354.0us (740.52MB/s)
Size = 524288
 Write 524288 = 1895.0us (276.67MB/s)
 Read 524288 = 783.0us (669.59MB/s)
Size = 1048576
 Write 1048576 = 2405.0us (436.00MB/s)
 Read 1048576 = 2872.0us (365.10MB/s)
Size = 2097152
 Write 2097152 = 4688.0us (447.34MB/s)
 Read 2097152 = 3398.0us (617.17MB/s)
Size = 4194304
 Write 4194304 = 10358.0us (404.93MB/s)
 Read 4194304 = 6556.0us (639.77MB/s)
Size = 8388608
 Write 8388608 = 19040.0us (440.58MB/s)
 Read 8388608 = 17753.0us (472.52MB/s)

TBR=digit@chromium.org  # per tools/OWNERS

Bug: 837659, 857070
Change-Id: I33fe4aeb287a5c5910c941a580093d9fecf8f758
Reviewed-on: https://chromium-review.googlesource.com/1116700
Commit-Queue: Benoit L <lizeb@chromium.org>
Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Reviewed-by: default avatarDavid Turner <digit@chromium.org>
Reviewed-by: default avatarEgor Pasko <pasko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#571077}
parent 94766d09
......@@ -71,3 +71,10 @@ group("push_apps_to_background") {
"//tools/android/push_apps_to_background:push_apps_to_background_apk",
]
}
group("io_benchmark") {
deps = [
"//tools/android/io_benchmark",
]
testonly = true
}
# 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.
executable("io_benchmark") {
sources = [
"io_benchmark.cc",
]
deps = [
"//base",
"//base/test:test_support",
]
testonly = true
}
lizeb@chromium.org
pasko@chromium.org
// 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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment