blob: cf38e4c5c834dd0eaf4193cb88df365b0baca2fb [file] [log] [blame]
Stefano Duo046c8542020-09-07 16:30:49 +00001/*
2 * Copyright (C) 2020 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#include <chrono>
17#include <functional>
18#include <iostream>
19#include <ratio>
20#include <sstream>
21#include <string>
22#include <unordered_map>
23#include <vector>
24
25#include <dirent.h>
26#include <fcntl.h>
27#include <stdlib.h>
28#include <sys/stat.h>
29#include <sys/types.h>
30#include <unistd.h>
31
32static constexpr char VERSION[] = "0";
33
34// Self-contained class for collecting and reporting benchmark metrics
35// (currently only execution time).
36class Collector {
37 using time_point = std::chrono::time_point<std::chrono::steady_clock>;
38 using time_unit = std::chrono::duration<double, std::milli>;
39
40 struct Metric {
41 std::string workload;
42 time_unit exec_time;
43 Metric(const std::string& workload, const time_unit& exec_time)
44 : workload(workload), exec_time(exec_time) {}
45 };
46
47 static constexpr char TIME_UNIT[] = "ms";
48 static constexpr char VERSION[] = "0";
49 std::vector<Metric> metrics;
50 time_point reset_time;
51
52 public:
53 Collector() { reset(); }
54
55 void reset() { reset_time = std::chrono::steady_clock::now(); }
56
57 void collect_metric(const std::string& workload) {
58 auto elapsed = std::chrono::steady_clock::now() - reset_time;
59 metrics.emplace_back(workload, std::chrono::duration_cast<time_unit>(elapsed));
60 }
61
62 void report_metrics() {
63 for (const Metric& metric : metrics)
64 std::cout << VERSION << ";" << metric.workload << ";" << metric.exec_time.count() << ";"
65 << TIME_UNIT << std::endl;
66 }
67};
68
69struct Command {
70 static constexpr char CREATE[] = "create";
71 static constexpr char DELETE[] = "delete";
72 static constexpr char MOVE[] = "move";
73 static constexpr char HARDLINK[] = "hardlink";
74 static constexpr char SYMLINK[] = "symlink";
75 static constexpr char READDIR[] = "readdir";
76 std::string workload;
77 std::string from_dir;
78 std::string from_basename;
79 std::string to_dir;
80 std::string to_basename;
81 bool drop_state;
82 int n_file;
83
84 Command() { reset(); }
85
86 std::string to_string() const {
87 std::stringstream string_repr;
88 string_repr << "Command {\n";
89 string_repr << "\t.workload = " << workload << ",\n";
90 string_repr << "\t.from_dir = " << from_dir << ",\n";
91 string_repr << "\t.from_basename = " << from_basename << ",\n";
92 string_repr << "\t.to_dir = " << to_dir << ",\n";
93 string_repr << "\t.to_basename = " << to_basename << ",\n";
94 string_repr << "\t.drop_state = " << drop_state << ",\n";
95 string_repr << "\t.n_file = " << n_file << "\n";
96 string_repr << "}\n";
97 return string_repr.str();
98 }
99
100 void reset() {
101 workload = "";
102 from_dir = to_dir = "./";
103 from_basename = "from_file";
104 to_basename = "to_file";
105 drop_state = true;
106 n_file = 0;
107 }
108};
109
110void print_version() {
111 std::cout << VERSION << std::endl;
112}
113
114void print_commands(const std::vector<Command>& commands) {
115 for (const Command& command : commands) std::cout << command.to_string();
116}
117
118void usage(std::ostream& ostr, const std::string& program_name) {
119 Command command;
120
121 ostr << "Usage: " << program_name << " [global_options] {[workload_options] -w WORKLOAD_T}\n";
122 ostr << "WORKLOAD_T = {" << Command::CREATE << ", " << Command::DELETE << ", " << Command::MOVE
123 << ", " << Command::HARDLINK << ", " << Command::SYMLINK << "}\n";
124 ostr << "Global options\n";
125 ostr << "\t-v: Print version.\n";
126 ostr << "\t-p: Print parsed workloads and exit.\n";
127 ostr << "Workload options\n";
128 ostr << "\t-d DIR\t\t: Work directory for " << Command::CREATE << "/" << Command::DELETE
129 << " (default '" << command.from_dir << "').\n";
130 ostr << "\t-f FROM-DIR\t: Source directory for " << Command::MOVE << "/" << Command::SYMLINK
131 << "/" << Command::HARDLINK << " (default '" << command.from_dir << "').\n";
132 ostr << "\t-t TO-DIR\t: Destination directory for " << Command::MOVE << "/" << Command::SYMLINK
133 << "/" << Command::HARDLINK << " (default '" << command.to_dir << "').\n";
134 ostr << "\t-n N_FILES\t: Number of files to create/delete etc. (default " << command.n_file
135 << ").\n";
136 ostr << "\t-s\t\t: Do not drop state (caches) before running the workload (default "
137 << !command.drop_state << ").\n";
138 ostr << "NOTE: -w WORKLOAD_T defines a new command and must come after its workload_options."
139 << std::endl;
140}
141
142void drop_state() {
143 // Drop inode/dentry/page caches.
144 std::system("sync; echo 3 > /proc/sys/vm/drop_caches");
145}
146
147static constexpr int OPEN_DIR_FLAGS = O_RDONLY | O_DIRECTORY | O_PATH | O_CLOEXEC;
148
Stefano Duo046c8542020-09-07 16:30:49 +0000149bool delete_files(const std::string& dir, int n_file, const std::string& basename) {
150 int dir_fd = open(dir.c_str(), OPEN_DIR_FLAGS);
151 if (dir_fd == -1) {
152 int error = errno;
153 std::cerr << "Failed to open work directory '" << dir << "', error '" << strerror(error)
154 << "'." << std::endl;
155 return false;
156 }
157
Stefano Duo00140192020-09-18 09:00:42 +0000158 bool ret = true;
Stefano Duo046c8542020-09-07 16:30:49 +0000159 for (int i = 0; i < n_file; i++) {
160 std::string filename = basename + std::to_string(i);
Stefano Duo00140192020-09-18 09:00:42 +0000161 ret = ret && (unlinkat(dir_fd, filename.c_str(), 0) == 0);
162 }
163
164 if (!ret) std::cerr << "Failed to delete at least one of the files" << std::endl;
165 close(dir_fd);
166 return ret;
167}
168
169bool create_files(const std::string& dir, int n_file, const std::string& basename) {
170 int dir_fd = open(dir.c_str(), OPEN_DIR_FLAGS);
171 if (dir_fd == -1) {
172 int error = errno;
173 std::cerr << "Failed to open work directory '" << dir << "', error '" << strerror(error)
174 << "'." << std::endl;
175 return false;
176 }
177
178 bool ret = true;
179 for (int i = 0; i < n_file; i++) {
180 std::string filename = basename + std::to_string(i);
181 int fd = openat(dir_fd, filename.c_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0777);
182 ret = ret && fd != -1;
183 close(fd);
Stefano Duo046c8542020-09-07 16:30:49 +0000184 }
185
186 close(dir_fd);
Stefano Duo00140192020-09-18 09:00:42 +0000187 if (!ret) {
188 std::cerr << "Failed to open at least one of the files" << std::endl;
189 delete_files(dir, n_file, basename);
190 }
191 return ret;
Stefano Duo046c8542020-09-07 16:30:49 +0000192}
193
194bool move_files(const std::string& from_dir, const std::string& to_dir, int n_file,
195 const std::string& from_basename, const std::string& to_basename) {
196 int from_dir_fd = open(from_dir.c_str(), OPEN_DIR_FLAGS);
197 if (from_dir_fd == -1) {
198 int error = errno;
199 std::cerr << "Failed to open source directory '" << from_dir << "', error '"
200 << strerror(error) << "'." << std::endl;
201 return false;
202 }
203 int to_dir_fd = open(to_dir.c_str(), OPEN_DIR_FLAGS);
204 if (to_dir_fd == -1) {
205 int error = errno;
206 std::cerr << "Failed to open destination directory '" << to_dir << "', error '"
207 << strerror(error) << "'." << std::endl;
208 close(from_dir_fd);
209 return false;
210 }
211
Stefano Duo00140192020-09-18 09:00:42 +0000212 bool ret = true;
Stefano Duo046c8542020-09-07 16:30:49 +0000213 for (int i = 0; i < n_file; i++) {
214 std::string from_filename = from_basename + std::to_string(i);
215 std::string to_filename = to_basename + std::to_string(i);
Stefano Duo00140192020-09-18 09:00:42 +0000216 ret = ret &&
217 (renameat(from_dir_fd, from_filename.c_str(), to_dir_fd, to_filename.c_str()) == 0);
Stefano Duo046c8542020-09-07 16:30:49 +0000218 }
219
Stefano Duo00140192020-09-18 09:00:42 +0000220 if (!ret) std::cerr << "Failed to move at least one of the files" << std::endl;
Stefano Duo046c8542020-09-07 16:30:49 +0000221 close(from_dir_fd);
222 close(from_dir_fd);
Stefano Duo00140192020-09-18 09:00:42 +0000223 return ret;
Stefano Duo046c8542020-09-07 16:30:49 +0000224}
225
226bool hardlink_files(const std::string& from_dir, const std::string& to_dir, int n_file,
227 const std::string& from_basename, const std::string& to_basename) {
228 int from_dir_fd = open(from_dir.c_str(), OPEN_DIR_FLAGS);
229 if (from_dir_fd == -1) {
230 int error = errno;
231 std::cerr << "Failed to open source directory '" << from_dir << "', error '"
232 << strerror(error) << "'." << std::endl;
233 return false;
234 }
235 int to_dir_fd = open(to_dir.c_str(), OPEN_DIR_FLAGS);
236 if (to_dir_fd == -1) {
237 int error = errno;
238 std::cerr << "Failed to open destination directory '" << to_dir << "', error '"
239 << strerror(error) << "'." << std::endl;
240 close(from_dir_fd);
241 return false;
242 }
243
Stefano Duo00140192020-09-18 09:00:42 +0000244 bool ret = true;
Stefano Duo046c8542020-09-07 16:30:49 +0000245 for (int i = 0; i < n_file; i++) {
246 std::string from_filename = from_basename + std::to_string(i);
247 std::string to_filename = to_basename + std::to_string(i);
Stefano Duo00140192020-09-18 09:00:42 +0000248 ret = ret &&
249 (linkat(from_dir_fd, from_filename.c_str(), to_dir_fd, to_filename.c_str(), 0) == 0);
Stefano Duo046c8542020-09-07 16:30:49 +0000250 }
251
Stefano Duo00140192020-09-18 09:00:42 +0000252 if (!ret) std::cerr << "Failed to hardlink at least one of the files" << std::endl;
Stefano Duo046c8542020-09-07 16:30:49 +0000253 close(from_dir_fd);
254 close(to_dir_fd);
Stefano Duo00140192020-09-18 09:00:42 +0000255 return ret;
Stefano Duo046c8542020-09-07 16:30:49 +0000256}
257
258bool symlink_files(std::string from_dir, const std::string& to_dir, int n_file,
259 const std::string& from_basename, const std::string& to_basename) {
260 if (from_dir.back() != '/') from_dir.push_back('/');
261 int to_dir_fd = open(to_dir.c_str(), OPEN_DIR_FLAGS);
262 if (to_dir_fd == -1) {
263 int error = errno;
264 std::cerr << "Failed to open destination directory '" << to_dir << "', error '"
265 << strerror(error) << "'." << std::endl;
266 return false;
267 }
268
Stefano Duo00140192020-09-18 09:00:42 +0000269 bool ret = true;
Stefano Duo046c8542020-09-07 16:30:49 +0000270 for (int i = 0; i < n_file; i++) {
271 std::string from_filepath = from_dir + from_basename + std::to_string(i);
272 std::string to_filename = to_basename + std::to_string(i);
Stefano Duo00140192020-09-18 09:00:42 +0000273 ret = ret && (symlinkat(from_filepath.c_str(), to_dir_fd, to_filename.c_str()) == 0);
Stefano Duo046c8542020-09-07 16:30:49 +0000274 }
275
Stefano Duo00140192020-09-18 09:00:42 +0000276 if (!ret) std::cerr << "Failed to symlink at least one of the files" << std::endl;
Stefano Duo046c8542020-09-07 16:30:49 +0000277 close(to_dir_fd);
Stefano Duo00140192020-09-18 09:00:42 +0000278 return ret;
Stefano Duo046c8542020-09-07 16:30:49 +0000279}
280
281bool exhaustive_readdir(const std::string& from_dir) {
282 DIR* dir = opendir(from_dir.c_str());
283 if (dir == nullptr) {
284 int error = errno;
285 std::cerr << "Failed to open working directory '" << from_dir << "', error '"
286 << strerror(error) << "'." << std::endl;
287 return false;
288 }
289
Stefano Duo00140192020-09-18 09:00:42 +0000290 errno = 0;
Stefano Duo046c8542020-09-07 16:30:49 +0000291 while (readdir(dir) != nullptr)
292 ;
Stefano Duo00140192020-09-18 09:00:42 +0000293 // In case of failure readdir returns nullptr and sets errno accordingly (to
294 // something != 0).
295 // In case of success readdir != nullptr and errno is not changed.
296 // Source: man 3 readdir.
297 bool ret = errno == 0;
Stefano Duo046c8542020-09-07 16:30:49 +0000298 closedir(dir);
Stefano Duo00140192020-09-18 09:00:42 +0000299 return ret;
Stefano Duo046c8542020-09-07 16:30:49 +0000300}
301
302void create_workload(Collector* collector, const Command& command) {
303 if (command.drop_state) drop_state();
304 collector->reset();
305 if (create_files(command.from_dir, command.n_file, command.from_basename))
306 collector->collect_metric(command.workload);
307
308 delete_files(command.from_dir, command.n_file, command.from_basename);
309}
310
311void delete_workload(Collector* collector, const Command& command) {
312 if (!create_files(command.from_dir, command.n_file, command.from_basename)) return;
313
314 if (command.drop_state) drop_state();
315 collector->reset();
316 if (delete_files(command.from_dir, command.n_file, command.from_basename))
317 collector->collect_metric(command.workload);
318}
319
320void move_workload(Collector* collector, const Command& command) {
321 if (!create_files(command.from_dir, command.n_file, command.from_basename)) return;
322
323 if (command.drop_state) drop_state();
324 collector->reset();
325 if (move_files(command.from_dir, command.to_dir, command.n_file, command.from_basename,
326 command.to_basename))
327 collector->collect_metric(command.workload);
328
329 delete_files(command.to_dir, command.n_file, command.to_basename);
330}
331
332void hardlink_workload(Collector* collector, const Command& command) {
333 if (!create_files(command.from_dir, command.n_file, command.from_basename)) return;
334
335 if (command.drop_state) drop_state();
336 collector->reset();
337 if (hardlink_files(command.from_dir, command.to_dir, command.n_file, command.from_basename,
338 command.to_basename))
339 collector->collect_metric(command.workload);
340
341 delete_files(command.from_dir, command.n_file, command.from_basename);
342 delete_files(command.to_dir, command.n_file, command.to_basename);
343}
344
345void symlink_workload(Collector* collector, const Command& command) {
346 if (!create_files(command.from_dir, command.n_file, command.from_basename)) return;
347
348 if (command.drop_state) drop_state();
349 collector->reset();
350 if (symlink_files(command.from_dir, command.to_dir, command.n_file, command.from_basename,
351 command.to_basename))
352 collector->collect_metric(command.workload);
353
354 delete_files(command.to_dir, command.n_file, command.to_basename);
355 delete_files(command.from_dir, command.n_file, command.from_basename);
356}
357
358void readdir_workload(Collector* collector, const Command& command) {
359 if (!create_files(command.from_dir, command.n_file, command.from_basename)) return;
360
361 if (command.drop_state) drop_state();
362 collector->reset();
363 if (exhaustive_readdir(command.from_dir)) collector->collect_metric(command.workload);
364
365 delete_files(command.from_dir, command.n_file, command.from_basename);
366}
367
368using workload_executor_t = std::function<void(Collector*, const Command&)>;
369
370std::unordered_map<std::string, workload_executor_t> executors = {
371 {Command::CREATE, create_workload}, {Command::DELETE, delete_workload},
372 {Command::MOVE, move_workload}, {Command::HARDLINK, hardlink_workload},
373 {Command::SYMLINK, symlink_workload}, {Command::READDIR, readdir_workload}};
374
375int main(int argc, char** argv) {
376 std::vector<Command> commands;
377 Command command;
378 int opt;
379
380 while ((opt = getopt(argc, argv, "hvpsw:d:f:t:n:")) != -1) {
381 switch (opt) {
382 case 'h':
383 usage(std::cout, argv[0]);
384 return EXIT_SUCCESS;
385 case 'v':
386 print_version();
387 return EXIT_SUCCESS;
388 case 'p':
389 print_commands(commands);
390 return EXIT_SUCCESS;
391 case 's':
392 command.drop_state = false;
393 break;
394 case 'w':
395 command.workload = optarg;
396 commands.push_back(command);
397 command.reset();
398 break;
399 case 'd':
400 case 'f':
401 command.from_dir = optarg;
402 break;
403 case 't':
404 command.to_dir = optarg;
405 break;
406 case 'n':
407 command.n_file = std::stoi(optarg);
408 break;
409 default:
410 usage(std::cerr, argv[0]);
411 return EXIT_FAILURE;
412 }
413 }
414
415 Collector collector;
416 for (const Command& command : commands) {
417 auto executor = executors.find(command.workload);
418 if (executor == executors.end()) continue;
419 executor->second(&collector, command);
420 }
421 collector.report_metrics();
422
423 return EXIT_SUCCESS;
424}