1 /*
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 
32 static constexpr char VERSION[] = "0";
33 
34 // Self-contained class for collecting and reporting benchmark metrics
35 // (currently only execution time).
36 class 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;
MetricCollector::Metric43         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:
Collector()53     Collector() { reset(); }
54 
reset()55     void reset() { reset_time = std::chrono::steady_clock::now(); }
56 
collect_metric(const std::string & workload)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 
report_metrics()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 
69 struct 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 
CommandCommand84     Command() { reset(); }
85 
to_stringCommand86     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 
resetCommand100     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 
print_version()110 void print_version() {
111     std::cout << VERSION << std::endl;
112 }
113 
print_commands(const std::vector<Command> & commands)114 void print_commands(const std::vector<Command>& commands) {
115     for (const Command& command : commands) std::cout << command.to_string();
116 }
117 
usage(std::ostream & ostr,const std::string & program_name)118 void 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 
drop_state()142 void drop_state() {
143     // Drop inode/dentry/page caches.
144     std::system("sync; echo 3 > /proc/sys/vm/drop_caches");
145 }
146 
147 static constexpr int OPEN_DIR_FLAGS = O_RDONLY | O_DIRECTORY | O_PATH | O_CLOEXEC;
148 
delete_files(const std::string & dir,int n_file,const std::string & basename)149 bool 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 
158     bool ret = true;
159     for (int i = 0; i < n_file; i++) {
160         std::string filename = basename + std::to_string(i);
161         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 
create_files(const std::string & dir,int n_file,const std::string & basename)169 bool 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);
184     }
185 
186     close(dir_fd);
187     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;
192 }
193 
move_files(const std::string & from_dir,const std::string & to_dir,int n_file,const std::string & from_basename,const std::string & to_basename)194 bool 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 
212     bool ret = true;
213     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);
216         ret = ret &&
217               (renameat(from_dir_fd, from_filename.c_str(), to_dir_fd, to_filename.c_str()) == 0);
218     }
219 
220     if (!ret) std::cerr << "Failed to move at least one of the files" << std::endl;
221     close(from_dir_fd);
222     close(from_dir_fd);
223     return ret;
224 }
225 
hardlink_files(const std::string & from_dir,const std::string & to_dir,int n_file,const std::string & from_basename,const std::string & to_basename)226 bool 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 
244     bool ret = true;
245     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);
248         ret = ret &&
249               (linkat(from_dir_fd, from_filename.c_str(), to_dir_fd, to_filename.c_str(), 0) == 0);
250     }
251 
252     if (!ret) std::cerr << "Failed to hardlink at least one of the files" << std::endl;
253     close(from_dir_fd);
254     close(to_dir_fd);
255     return ret;
256 }
257 
symlink_files(std::string from_dir,const std::string & to_dir,int n_file,const std::string & from_basename,const std::string & to_basename)258 bool 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 
269     bool ret = true;
270     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);
273         ret = ret && (symlinkat(from_filepath.c_str(), to_dir_fd, to_filename.c_str()) == 0);
274     }
275 
276     if (!ret) std::cerr << "Failed to symlink at least one of the files" << std::endl;
277     close(to_dir_fd);
278     return ret;
279 }
280 
exhaustive_readdir(const std::string & from_dir)281 bool 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 
290     errno = 0;
291     while (readdir(dir) != nullptr)
292         ;
293     // 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;
298     closedir(dir);
299     return ret;
300 }
301 
create_workload(Collector * collector,const Command & command)302 void 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 
delete_workload(Collector * collector,const Command & command)311 void 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 
move_workload(Collector * collector,const Command & command)320 void 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 
hardlink_workload(Collector * collector,const Command & command)332 void 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 
symlink_workload(Collector * collector,const Command & command)345 void 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 
readdir_workload(Collector * collector,const Command & command)358 void 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 
368 using workload_executor_t = std::function<void(Collector*, const Command&)>;
369 
370 std::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 
main(int argc,char ** argv)375 int 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 }
425