blob: d4a02b0db1a0b832de50d641dddba68303aed969 [file] [log] [blame]
Inseob Kimfd922cf2021-10-18 19:49:00 +09001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <android-base/result.h>
18#include <android-base/unique_fd.h>
19#include <linux/vm_sockets.h>
20#include <strings.h>
21#include <sys/stat.h>
22#include <unistd.h>
23
24#include <algorithm>
25#include <cerrno>
26#include <cstdint>
27#include <cstdio>
28#include <cstdlib>
29#include <random>
30#include <string>
31#include <vector>
32
33using android::base::ErrnoError;
34using android::base::Error;
35using android::base::Result;
36using android::base::unique_fd;
37
38namespace {
39
40constexpr int kBlockSize = 4096;
41
42[[noreturn]] void PrintUsage(const char* exe_name) {
43 std::printf("Usage: %s path size (read|write|both) [rounds]\n", exe_name);
44 std::exit(EXIT_FAILURE);
45}
46
47void DropCache() {
48 system("echo 1 > /proc/sys/vm/drop_caches");
49}
50
51struct BenchmarkResult {
52 struct timespec elapsed;
53 std::uint64_t size;
54};
55
56enum class BenchmarkOption {
57 READ = 0,
58 WRITE = 1,
59 RANDREAD = 2,
60 RANDWRITE = 3,
61};
62
63Result<BenchmarkResult> runTest(const char* path, BenchmarkOption option, std::uint64_t size) {
64 bool is_read = (option == BenchmarkOption::READ || option == BenchmarkOption::RANDREAD);
65 bool is_rand = (option == BenchmarkOption::RANDREAD || option == BenchmarkOption::RANDWRITE);
66
67 unique_fd fd(open(path, is_read ? O_RDONLY : O_WRONLY | O_CREAT, 0644));
68 if (fd.get() == -1) {
69 return ErrnoError() << "opening " << path << " failed";
70 }
71
72 uint64_t block_count = (size + kBlockSize - 1) / kBlockSize;
73 std::vector<uint64_t> offsets;
74 if (is_rand) {
75 std::mt19937 rd{std::random_device{}()};
76 offsets.reserve(block_count);
77 for (uint64_t i = 0; i < block_count; i++) offsets.push_back(i * kBlockSize);
78 std::shuffle(offsets.begin(), offsets.end(), rd);
79 }
80
81 uint64_t total_processed = 0;
82 char buf[kBlockSize] = {};
83
84 struct timespec start;
85 if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
86 return ErrnoError() << "failed to get start time";
87 }
88
89 for (uint64_t i = 0; i < block_count; i++) {
90 if (!offsets.empty()) {
91 if (lseek(fd.get(), offsets[i], SEEK_SET) == -1) {
92 return ErrnoError() << "failed to lseek";
93 }
94 }
95
96 auto ret = is_read ? read(fd.get(), buf, kBlockSize) : write(fd.get(), buf, kBlockSize);
97 if (ret == 0) {
98 return Error() << "unexpected end of file";
99 } else if (ret == -1) {
100 return ErrnoError() << "file io failed";
101 }
102 total_processed += ret;
103 }
104
105 struct timespec stop;
106 if (clock_gettime(CLOCK_REALTIME, &stop) < 0) {
107 return ErrnoError() << "failed to get finish time";
108 }
109
110 struct timespec elapsed;
111 if ((stop.tv_nsec - start.tv_nsec) < 0) {
112 elapsed.tv_sec = stop.tv_sec - start.tv_sec - 1;
113 elapsed.tv_nsec = stop.tv_nsec - start.tv_nsec + 1000000000;
114 } else {
115 elapsed.tv_sec = stop.tv_sec - start.tv_sec;
116 elapsed.tv_nsec = stop.tv_nsec - start.tv_nsec;
117 }
118
119 return BenchmarkResult{elapsed, total_processed};
120}
121
122} // namespace
123
124int main(int argc, char* argv[]) {
125 // without this, stdout isn't immediately flushed when running via "adb shell"
126 std::setvbuf(stdout, nullptr, _IONBF, 0);
127 std::setvbuf(stderr, nullptr, _IONBF, 0);
128
129 if (argc < 4 || argc > 5) {
130 PrintUsage(argv[0]);
131 }
132
133 const char* path = argv[1];
134
135 std::uint64_t size = std::strtoull(argv[2], nullptr, 0);
136 if (size == 0 || size == UINT64_MAX) {
137 std::fprintf(stderr, "invalid size %s\n", argv[1]);
138 PrintUsage(argv[0]);
139 }
140
141 std::vector<std::pair<BenchmarkOption, std::string>> benchmarkList;
142 if (strcmp(argv[3], "read") != 0) {
143 benchmarkList.emplace_back(BenchmarkOption::WRITE, "write");
144 benchmarkList.emplace_back(BenchmarkOption::RANDWRITE, "randwrite");
145 }
146 if (strcmp(argv[3], "write") != 0) {
147 benchmarkList.emplace_back(BenchmarkOption::READ, "read");
148 benchmarkList.emplace_back(BenchmarkOption::RANDREAD, "randread");
149 }
150
151 std::shuffle(benchmarkList.begin(), benchmarkList.end(), std::mt19937{std::random_device{}()});
152
153 int rounds = 1;
154 if (argc == 5) {
155 rounds = std::atoi(argv[4]);
156 if (rounds <= 0) {
157 std::fprintf(stderr, "invalid round %s\n", argv[4]);
158 PrintUsage(argv[0]);
159 }
160 }
161
162 for (auto [option, name] : benchmarkList) {
163 std::printf("%s test:\n", name.c_str());
164
165 for (int i = 0; i < rounds; i++) {
166 DropCache();
167 auto res = runTest(path, option, size);
168 if (!res.ok()) {
169 std::fprintf(stderr, "Error while benchmarking: %s\n",
170 res.error().message().c_str());
171 return EXIT_FAILURE;
172 }
173
174 double elapsed_time = res->elapsed.tv_sec + res->elapsed.tv_nsec / 1e9;
175 std::printf("total %zu bytes, took %.3g seconds ", res->size, elapsed_time);
176
177 double speed = res->size / elapsed_time;
178 const char* unit;
179 if (speed >= 1000) {
180 speed /= 1024;
181 unit = "KB";
182 }
183 if (speed >= 1000) {
184 speed /= 1024;
185 unit = "MB";
186 }
187 if (speed >= 1000) {
188 speed /= 1024;
189 unit = "GB";
190 }
191 std::printf("(%.3g %s/s)\n", speed, unit);
192 }
193 std::printf("\n");
194 }
195}