/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 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> 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"); } }