• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 <cinttypes>
27 #include <cstdint>
28 #include <cstdio>
29 #include <cstdlib>
30 #include <random>
31 #include <string>
32 #include <vector>
33 
34 using android::base::ErrnoError;
35 using android::base::Error;
36 using android::base::Result;
37 using android::base::unique_fd;
38 
39 namespace {
40 
41 constexpr int kBlockSize = 4096;
42 
PrintUsage(const char * exe_name)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 
DropCache()48 void DropCache() {
49     system("echo 1 > /proc/sys/vm/drop_caches");
50 }
51 
52 struct BenchmarkResult {
53     struct timespec elapsed;
54     std::uint64_t size;
55 };
56 
57 enum class BenchmarkOption {
58     READ = 0,
59     WRITE = 1,
60     RANDREAD = 2,
61     RANDWRITE = 3,
62 };
63 
runTest(const char * path,BenchmarkOption option,std::uint64_t size)64 Result<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 
main(int argc,char * argv[])125 int 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;
176             std::printf("total %" PRIu64 " bytes, took %.3g seconds ", res->size, elapsed_time);
177 
178             double speed = res->size / elapsed_time;
179             const char* unit = "bytes";
180             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 }
197