blob: 220e004d5c707fc1c98ee36436b35bfec2d96f03 [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>
Inseob Kim4da90552021-11-11 20:36:10 +090026#include <cinttypes>
Inseob Kimfd922cf2021-10-18 19:49:00 +090027#include <cstdint>
28#include <cstdio>
29#include <cstdlib>
30#include <random>
31#include <string>
32#include <vector>
33
34using android::base::ErrnoError;
35using android::base::Error;
36using android::base::Result;
37using android::base::unique_fd;
38
39namespace {
40
41constexpr int kBlockSize = 4096;
42
43[[noreturn]] void PrintUsage(const char* exe_name) {
44 std::printf("Usage: %s path size (read|write|both) [rounds]\n", exe_name);
45 std::exit(EXIT_FAILURE);
46}
47
48void DropCache() {
49 system("echo 1 > /proc/sys/vm/drop_caches");
50}
51
52struct BenchmarkResult {
53 struct timespec elapsed;
54 std::uint64_t size;
55};
56
57enum class BenchmarkOption {
58 READ = 0,
59 WRITE = 1,
60 RANDREAD = 2,
61 RANDWRITE = 3,
62};
63
64Result<BenchmarkResult> runTest(const char* path, BenchmarkOption option, std::uint64_t size) {
65 bool is_read = (option == BenchmarkOption::READ || option == BenchmarkOption::RANDREAD);
66 bool is_rand = (option == BenchmarkOption::RANDREAD || option == BenchmarkOption::RANDWRITE);
67
68 unique_fd fd(open(path, is_read ? O_RDONLY : O_WRONLY | O_CREAT, 0644));
69 if (fd.get() == -1) {
70 return ErrnoError() << "opening " << path << " failed";
71 }
72
73 uint64_t block_count = (size + kBlockSize - 1) / kBlockSize;
74 std::vector<uint64_t> offsets;
75 if (is_rand) {
76 std::mt19937 rd{std::random_device{}()};
77 offsets.reserve(block_count);
78 for (uint64_t i = 0; i < block_count; i++) offsets.push_back(i * kBlockSize);
79 std::shuffle(offsets.begin(), offsets.end(), rd);
80 }
81
82 uint64_t total_processed = 0;
83 char buf[kBlockSize] = {};
84
85 struct timespec start;
86 if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
87 return ErrnoError() << "failed to get start time";
88 }
89
90 for (uint64_t i = 0; i < block_count; i++) {
91 if (!offsets.empty()) {
92 if (lseek(fd.get(), offsets[i], SEEK_SET) == -1) {
93 return ErrnoError() << "failed to lseek";
94 }
95 }
96
97 auto ret = is_read ? read(fd.get(), buf, kBlockSize) : write(fd.get(), buf, kBlockSize);
98 if (ret == 0) {
99 return Error() << "unexpected end of file";
100 } else if (ret == -1) {
101 return ErrnoError() << "file io failed";
102 }
103 total_processed += ret;
104 }
105
106 struct timespec stop;
107 if (clock_gettime(CLOCK_REALTIME, &stop) < 0) {
108 return ErrnoError() << "failed to get finish time";
109 }
110
111 struct timespec elapsed;
112 if ((stop.tv_nsec - start.tv_nsec) < 0) {
113 elapsed.tv_sec = stop.tv_sec - start.tv_sec - 1;
114 elapsed.tv_nsec = stop.tv_nsec - start.tv_nsec + 1000000000;
115 } else {
116 elapsed.tv_sec = stop.tv_sec - start.tv_sec;
117 elapsed.tv_nsec = stop.tv_nsec - start.tv_nsec;
118 }
119
120 return BenchmarkResult{elapsed, total_processed};
121}
122
123} // namespace
124
125int main(int argc, char* argv[]) {
126 // without this, stdout isn't immediately flushed when running via "adb shell"
127 std::setvbuf(stdout, nullptr, _IONBF, 0);
128 std::setvbuf(stderr, nullptr, _IONBF, 0);
129
130 if (argc < 4 || argc > 5) {
131 PrintUsage(argv[0]);
132 }
133
134 const char* path = argv[1];
135
136 std::uint64_t size = std::strtoull(argv[2], nullptr, 0);
137 if (size == 0 || size == UINT64_MAX) {
138 std::fprintf(stderr, "invalid size %s\n", argv[1]);
139 PrintUsage(argv[0]);
140 }
141
142 std::vector<std::pair<BenchmarkOption, std::string>> benchmarkList;
143 if (strcmp(argv[3], "read") != 0) {
144 benchmarkList.emplace_back(BenchmarkOption::WRITE, "write");
145 benchmarkList.emplace_back(BenchmarkOption::RANDWRITE, "randwrite");
146 }
147 if (strcmp(argv[3], "write") != 0) {
148 benchmarkList.emplace_back(BenchmarkOption::READ, "read");
149 benchmarkList.emplace_back(BenchmarkOption::RANDREAD, "randread");
150 }
151
152 std::shuffle(benchmarkList.begin(), benchmarkList.end(), std::mt19937{std::random_device{}()});
153
154 int rounds = 1;
155 if (argc == 5) {
156 rounds = std::atoi(argv[4]);
157 if (rounds <= 0) {
158 std::fprintf(stderr, "invalid round %s\n", argv[4]);
159 PrintUsage(argv[0]);
160 }
161 }
162
163 for (auto [option, name] : benchmarkList) {
164 std::printf("%s test:\n", name.c_str());
165
166 for (int i = 0; i < rounds; i++) {
167 DropCache();
168 auto res = runTest(path, option, size);
169 if (!res.ok()) {
170 std::fprintf(stderr, "Error while benchmarking: %s\n",
171 res.error().message().c_str());
172 return EXIT_FAILURE;
173 }
174
175 double elapsed_time = res->elapsed.tv_sec + res->elapsed.tv_nsec / 1e9;
Inseob Kim4da90552021-11-11 20:36:10 +0900176 std::printf("total %" PRIu64 " bytes, took %.3g seconds ", res->size, elapsed_time);
Inseob Kimfd922cf2021-10-18 19:49:00 +0900177
178 double speed = res->size / elapsed_time;
Inseob Kimbcec5452021-11-12 10:53:53 +0900179 const char* unit = "bytes";
Inseob Kimfd922cf2021-10-18 19:49:00 +0900180 if (speed >= 1000) {
181 speed /= 1024;
182 unit = "KB";
183 }
184 if (speed >= 1000) {
185 speed /= 1024;
186 unit = "MB";
187 }
188 if (speed >= 1000) {
189 speed /= 1024;
190 unit = "GB";
191 }
192 std::printf("(%.3g %s/s)\n", speed, unit);
193 }
194 std::printf("\n");
195 }
196}