197 lines
5.9 KiB
C++
197 lines
5.9 KiB
C++
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <android-base/result.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <linux/vm_sockets.h>
|
|
#include <strings.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <cinttypes>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <random>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using android::base::ErrnoError;
|
|
using android::base::Error;
|
|
using android::base::Result;
|
|
using android::base::unique_fd;
|
|
|
|
namespace {
|
|
|
|
constexpr int kBlockSize = 4096;
|
|
|
|
[[noreturn]] void PrintUsage(const char* exe_name) {
|
|
std::printf("Usage: %s path size (read|write|both) [rounds]\n", exe_name);
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
|
|
void DropCache() {
|
|
system("echo 1 > /proc/sys/vm/drop_caches");
|
|
}
|
|
|
|
struct BenchmarkResult {
|
|
struct timespec elapsed;
|
|
std::uint64_t size;
|
|
};
|
|
|
|
enum class BenchmarkOption {
|
|
READ = 0,
|
|
WRITE = 1,
|
|
RANDREAD = 2,
|
|
RANDWRITE = 3,
|
|
};
|
|
|
|
Result<BenchmarkResult> runTest(const char* path, BenchmarkOption option, std::uint64_t size) {
|
|
bool is_read = (option == BenchmarkOption::READ || option == BenchmarkOption::RANDREAD);
|
|
bool is_rand = (option == BenchmarkOption::RANDREAD || option == BenchmarkOption::RANDWRITE);
|
|
|
|
unique_fd fd(open(path, is_read ? O_RDONLY : O_WRONLY | O_CREAT, 0644));
|
|
if (fd.get() == -1) {
|
|
return ErrnoError() << "opening " << path << " failed";
|
|
}
|
|
|
|
uint64_t block_count = (size + kBlockSize - 1) / kBlockSize;
|
|
std::vector<uint64_t> offsets;
|
|
if (is_rand) {
|
|
std::mt19937 rd{std::random_device{}()};
|
|
offsets.reserve(block_count);
|
|
for (uint64_t i = 0; i < block_count; i++) offsets.push_back(i * kBlockSize);
|
|
std::shuffle(offsets.begin(), offsets.end(), rd);
|
|
}
|
|
|
|
uint64_t total_processed = 0;
|
|
char buf[kBlockSize] = {};
|
|
|
|
struct timespec start;
|
|
if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
|
|
return ErrnoError() << "failed to get start time";
|
|
}
|
|
|
|
for (uint64_t i = 0; i < block_count; i++) {
|
|
if (!offsets.empty()) {
|
|
if (lseek(fd.get(), offsets[i], SEEK_SET) == -1) {
|
|
return ErrnoError() << "failed to lseek";
|
|
}
|
|
}
|
|
|
|
auto ret = is_read ? read(fd.get(), buf, kBlockSize) : write(fd.get(), buf, kBlockSize);
|
|
if (ret == 0) {
|
|
return Error() << "unexpected end of file";
|
|
} else if (ret == -1) {
|
|
return ErrnoError() << "file io failed";
|
|
}
|
|
total_processed += ret;
|
|
}
|
|
|
|
struct timespec stop;
|
|
if (clock_gettime(CLOCK_REALTIME, &stop) < 0) {
|
|
return ErrnoError() << "failed to get finish time";
|
|
}
|
|
|
|
struct timespec elapsed;
|
|
if ((stop.tv_nsec - start.tv_nsec) < 0) {
|
|
elapsed.tv_sec = stop.tv_sec - start.tv_sec - 1;
|
|
elapsed.tv_nsec = stop.tv_nsec - start.tv_nsec + 1000000000;
|
|
} else {
|
|
elapsed.tv_sec = stop.tv_sec - start.tv_sec;
|
|
elapsed.tv_nsec = stop.tv_nsec - start.tv_nsec;
|
|
}
|
|
|
|
return BenchmarkResult{elapsed, total_processed};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char* argv[]) {
|
|
// without this, stdout isn't immediately flushed when running via "adb shell"
|
|
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
|
std::setvbuf(stderr, nullptr, _IONBF, 0);
|
|
|
|
if (argc < 4 || argc > 5) {
|
|
PrintUsage(argv[0]);
|
|
}
|
|
|
|
const char* path = argv[1];
|
|
|
|
std::uint64_t size = std::strtoull(argv[2], nullptr, 0);
|
|
if (size == 0 || size == UINT64_MAX) {
|
|
std::fprintf(stderr, "invalid size %s\n", argv[1]);
|
|
PrintUsage(argv[0]);
|
|
}
|
|
|
|
std::vector<std::pair<BenchmarkOption, std::string>> benchmarkList;
|
|
if (strcmp(argv[3], "read") != 0) {
|
|
benchmarkList.emplace_back(BenchmarkOption::WRITE, "write");
|
|
benchmarkList.emplace_back(BenchmarkOption::RANDWRITE, "randwrite");
|
|
}
|
|
if (strcmp(argv[3], "write") != 0) {
|
|
benchmarkList.emplace_back(BenchmarkOption::READ, "read");
|
|
benchmarkList.emplace_back(BenchmarkOption::RANDREAD, "randread");
|
|
}
|
|
|
|
std::shuffle(benchmarkList.begin(), benchmarkList.end(), std::mt19937{std::random_device{}()});
|
|
|
|
int rounds = 1;
|
|
if (argc == 5) {
|
|
rounds = std::atoi(argv[4]);
|
|
if (rounds <= 0) {
|
|
std::fprintf(stderr, "invalid round %s\n", argv[4]);
|
|
PrintUsage(argv[0]);
|
|
}
|
|
}
|
|
|
|
for (auto [option, name] : benchmarkList) {
|
|
std::printf("%s test:\n", name.c_str());
|
|
|
|
for (int i = 0; i < rounds; i++) {
|
|
DropCache();
|
|
auto res = runTest(path, option, size);
|
|
if (!res.ok()) {
|
|
std::fprintf(stderr, "Error while benchmarking: %s\n",
|
|
res.error().message().c_str());
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
double elapsed_time = res->elapsed.tv_sec + res->elapsed.tv_nsec / 1e9;
|
|
std::printf("total %" PRIu64 " bytes, took %.3g seconds ", res->size, elapsed_time);
|
|
|
|
double speed = res->size / elapsed_time;
|
|
const char* unit = "bytes";
|
|
if (speed >= 1000) {
|
|
speed /= 1024;
|
|
unit = "KB";
|
|
}
|
|
if (speed >= 1000) {
|
|
speed /= 1024;
|
|
unit = "MB";
|
|
}
|
|
if (speed >= 1000) {
|
|
speed /= 1024;
|
|
unit = "GB";
|
|
}
|
|
std::printf("(%.3g %s/s)\n", speed, unit);
|
|
}
|
|
std::printf("\n");
|
|
}
|
|
}
|