| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include <chrono> |
| #include <functional> |
| #include <iostream> |
| #include <ratio> |
| #include <sstream> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| static constexpr char VERSION[] = "0"; |
| |
| // Self-contained class for collecting and reporting benchmark metrics |
| // (currently only execution time). |
| class Collector { |
| using time_point = std::chrono::time_point<std::chrono::steady_clock>; |
| using time_unit = std::chrono::duration<double, std::milli>; |
| |
| struct Metric { |
| std::string workload; |
| time_unit exec_time; |
| Metric(const std::string& workload, const time_unit& exec_time) |
| : workload(workload), exec_time(exec_time) {} |
| }; |
| |
| static constexpr char TIME_UNIT[] = "ms"; |
| static constexpr char VERSION[] = "0"; |
| std::vector<Metric> metrics; |
| time_point reset_time; |
| |
| public: |
| Collector() { reset(); } |
| |
| void reset() { reset_time = std::chrono::steady_clock::now(); } |
| |
| void collect_metric(const std::string& workload) { |
| auto elapsed = std::chrono::steady_clock::now() - reset_time; |
| metrics.emplace_back(workload, std::chrono::duration_cast<time_unit>(elapsed)); |
| } |
| |
| void report_metrics() { |
| for (const Metric& metric : metrics) |
| std::cout << VERSION << ";" << metric.workload << ";" << metric.exec_time.count() << ";" |
| << TIME_UNIT << std::endl; |
| } |
| }; |
| |
| struct Command { |
| static constexpr char CREATE[] = "create"; |
| static constexpr char DELETE[] = "delete"; |
| static constexpr char MOVE[] = "move"; |
| static constexpr char HARDLINK[] = "hardlink"; |
| static constexpr char SYMLINK[] = "symlink"; |
| static constexpr char READDIR[] = "readdir"; |
| std::string workload; |
| std::string from_dir; |
| std::string from_basename; |
| std::string to_dir; |
| std::string to_basename; |
| bool drop_state; |
| int n_file; |
| |
| Command() { reset(); } |
| |
| std::string to_string() const { |
| std::stringstream string_repr; |
| string_repr << "Command {\n"; |
| string_repr << "\t.workload = " << workload << ",\n"; |
| string_repr << "\t.from_dir = " << from_dir << ",\n"; |
| string_repr << "\t.from_basename = " << from_basename << ",\n"; |
| string_repr << "\t.to_dir = " << to_dir << ",\n"; |
| string_repr << "\t.to_basename = " << to_basename << ",\n"; |
| string_repr << "\t.drop_state = " << drop_state << ",\n"; |
| string_repr << "\t.n_file = " << n_file << "\n"; |
| string_repr << "}\n"; |
| return string_repr.str(); |
| } |
| |
| void reset() { |
| workload = ""; |
| from_dir = to_dir = "./"; |
| from_basename = "from_file"; |
| to_basename = "to_file"; |
| drop_state = true; |
| n_file = 0; |
| } |
| }; |
| |
| void print_version() { |
| std::cout << VERSION << std::endl; |
| } |
| |
| void print_commands(const std::vector<Command>& commands) { |
| for (const Command& command : commands) std::cout << command.to_string(); |
| } |
| |
| void usage(std::ostream& ostr, const std::string& program_name) { |
| Command command; |
| |
| ostr << "Usage: " << program_name << " [global_options] {[workload_options] -w WORKLOAD_T}\n"; |
| ostr << "WORKLOAD_T = {" << Command::CREATE << ", " << Command::DELETE << ", " << Command::MOVE |
| << ", " << Command::HARDLINK << ", " << Command::SYMLINK << "}\n"; |
| ostr << "Global options\n"; |
| ostr << "\t-v: Print version.\n"; |
| ostr << "\t-p: Print parsed workloads and exit.\n"; |
| ostr << "Workload options\n"; |
| ostr << "\t-d DIR\t\t: Work directory for " << Command::CREATE << "/" << Command::DELETE |
| << " (default '" << command.from_dir << "').\n"; |
| ostr << "\t-f FROM-DIR\t: Source directory for " << Command::MOVE << "/" << Command::SYMLINK |
| << "/" << Command::HARDLINK << " (default '" << command.from_dir << "').\n"; |
| ostr << "\t-t TO-DIR\t: Destination directory for " << Command::MOVE << "/" << Command::SYMLINK |
| << "/" << Command::HARDLINK << " (default '" << command.to_dir << "').\n"; |
| ostr << "\t-n N_FILES\t: Number of files to create/delete etc. (default " << command.n_file |
| << ").\n"; |
| ostr << "\t-s\t\t: Do not drop state (caches) before running the workload (default " |
| << !command.drop_state << ").\n"; |
| ostr << "NOTE: -w WORKLOAD_T defines a new command and must come after its workload_options." |
| << std::endl; |
| } |
| |
| void drop_state() { |
| // Drop inode/dentry/page caches. |
| std::system("sync; echo 3 > /proc/sys/vm/drop_caches"); |
| } |
| |
| static constexpr int OPEN_DIR_FLAGS = O_RDONLY | O_DIRECTORY | O_PATH | O_CLOEXEC; |
| |
| bool create_files(const std::string& dir, int n_file, const std::string& basename) { |
| int dir_fd = open(dir.c_str(), OPEN_DIR_FLAGS); |
| if (dir_fd == -1) { |
| int error = errno; |
| std::cerr << "Failed to open work directory '" << dir << "', error '" << strerror(error) |
| << "'." << std::endl; |
| return false; |
| } |
| |
| for (int i = 0; i < n_file; i++) { |
| std::string filename = basename + std::to_string(i); |
| int fd = openat(dir_fd, filename.c_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0777); |
| close(fd); |
| } |
| |
| close(dir_fd); |
| return true; |
| } |
| |
| bool delete_files(const std::string& dir, int n_file, const std::string& basename) { |
| int dir_fd = open(dir.c_str(), OPEN_DIR_FLAGS); |
| if (dir_fd == -1) { |
| int error = errno; |
| std::cerr << "Failed to open work directory '" << dir << "', error '" << strerror(error) |
| << "'." << std::endl; |
| return false; |
| } |
| |
| for (int i = 0; i < n_file; i++) { |
| std::string filename = basename + std::to_string(i); |
| unlinkat(dir_fd, filename.c_str(), 0); |
| } |
| |
| close(dir_fd); |
| return true; |
| } |
| |
| bool 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) { |
| int from_dir_fd = open(from_dir.c_str(), OPEN_DIR_FLAGS); |
| if (from_dir_fd == -1) { |
| int error = errno; |
| std::cerr << "Failed to open source directory '" << from_dir << "', error '" |
| << strerror(error) << "'." << std::endl; |
| return false; |
| } |
| int to_dir_fd = open(to_dir.c_str(), OPEN_DIR_FLAGS); |
| if (to_dir_fd == -1) { |
| int error = errno; |
| std::cerr << "Failed to open destination directory '" << to_dir << "', error '" |
| << strerror(error) << "'." << std::endl; |
| close(from_dir_fd); |
| return false; |
| } |
| |
| for (int i = 0; i < n_file; i++) { |
| std::string from_filename = from_basename + std::to_string(i); |
| std::string to_filename = to_basename + std::to_string(i); |
| renameat(from_dir_fd, from_filename.c_str(), to_dir_fd, to_filename.c_str()); |
| } |
| |
| close(from_dir_fd); |
| close(from_dir_fd); |
| return true; |
| } |
| |
| bool 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) { |
| int from_dir_fd = open(from_dir.c_str(), OPEN_DIR_FLAGS); |
| if (from_dir_fd == -1) { |
| int error = errno; |
| std::cerr << "Failed to open source directory '" << from_dir << "', error '" |
| << strerror(error) << "'." << std::endl; |
| return false; |
| } |
| int to_dir_fd = open(to_dir.c_str(), OPEN_DIR_FLAGS); |
| if (to_dir_fd == -1) { |
| int error = errno; |
| std::cerr << "Failed to open destination directory '" << to_dir << "', error '" |
| << strerror(error) << "'." << std::endl; |
| close(from_dir_fd); |
| return false; |
| } |
| |
| for (int i = 0; i < n_file; i++) { |
| std::string from_filename = from_basename + std::to_string(i); |
| std::string to_filename = to_basename + std::to_string(i); |
| linkat(from_dir_fd, from_filename.c_str(), to_dir_fd, to_filename.c_str(), 0); |
| } |
| |
| close(from_dir_fd); |
| close(to_dir_fd); |
| return true; |
| } |
| |
| bool symlink_files(std::string from_dir, const std::string& to_dir, int n_file, |
| const std::string& from_basename, const std::string& to_basename) { |
| if (from_dir.back() != '/') from_dir.push_back('/'); |
| int to_dir_fd = open(to_dir.c_str(), OPEN_DIR_FLAGS); |
| if (to_dir_fd == -1) { |
| int error = errno; |
| std::cerr << "Failed to open destination directory '" << to_dir << "', error '" |
| << strerror(error) << "'." << std::endl; |
| return false; |
| } |
| |
| for (int i = 0; i < n_file; i++) { |
| std::string from_filepath = from_dir + from_basename + std::to_string(i); |
| std::string to_filename = to_basename + std::to_string(i); |
| symlinkat(from_filepath.c_str(), to_dir_fd, to_filename.c_str()); |
| } |
| |
| close(to_dir_fd); |
| return true; |
| } |
| |
| bool exhaustive_readdir(const std::string& from_dir) { |
| DIR* dir = opendir(from_dir.c_str()); |
| if (dir == nullptr) { |
| int error = errno; |
| std::cerr << "Failed to open working directory '" << from_dir << "', error '" |
| << strerror(error) << "'." << std::endl; |
| return false; |
| } |
| |
| while (readdir(dir) != nullptr) |
| ; |
| |
| closedir(dir); |
| return true; |
| } |
| |
| void create_workload(Collector* collector, const Command& command) { |
| if (command.drop_state) drop_state(); |
| collector->reset(); |
| if (create_files(command.from_dir, command.n_file, command.from_basename)) |
| collector->collect_metric(command.workload); |
| |
| delete_files(command.from_dir, command.n_file, command.from_basename); |
| } |
| |
| void delete_workload(Collector* collector, const Command& command) { |
| if (!create_files(command.from_dir, command.n_file, command.from_basename)) return; |
| |
| if (command.drop_state) drop_state(); |
| collector->reset(); |
| if (delete_files(command.from_dir, command.n_file, command.from_basename)) |
| collector->collect_metric(command.workload); |
| } |
| |
| void move_workload(Collector* collector, const Command& command) { |
| if (!create_files(command.from_dir, command.n_file, command.from_basename)) return; |
| |
| if (command.drop_state) drop_state(); |
| collector->reset(); |
| if (move_files(command.from_dir, command.to_dir, command.n_file, command.from_basename, |
| command.to_basename)) |
| collector->collect_metric(command.workload); |
| |
| delete_files(command.to_dir, command.n_file, command.to_basename); |
| } |
| |
| void hardlink_workload(Collector* collector, const Command& command) { |
| if (!create_files(command.from_dir, command.n_file, command.from_basename)) return; |
| |
| if (command.drop_state) drop_state(); |
| collector->reset(); |
| if (hardlink_files(command.from_dir, command.to_dir, command.n_file, command.from_basename, |
| command.to_basename)) |
| collector->collect_metric(command.workload); |
| |
| delete_files(command.from_dir, command.n_file, command.from_basename); |
| delete_files(command.to_dir, command.n_file, command.to_basename); |
| } |
| |
| void symlink_workload(Collector* collector, const Command& command) { |
| if (!create_files(command.from_dir, command.n_file, command.from_basename)) return; |
| |
| if (command.drop_state) drop_state(); |
| collector->reset(); |
| if (symlink_files(command.from_dir, command.to_dir, command.n_file, command.from_basename, |
| command.to_basename)) |
| collector->collect_metric(command.workload); |
| |
| delete_files(command.to_dir, command.n_file, command.to_basename); |
| delete_files(command.from_dir, command.n_file, command.from_basename); |
| } |
| |
| void readdir_workload(Collector* collector, const Command& command) { |
| if (!create_files(command.from_dir, command.n_file, command.from_basename)) return; |
| |
| if (command.drop_state) drop_state(); |
| collector->reset(); |
| if (exhaustive_readdir(command.from_dir)) collector->collect_metric(command.workload); |
| |
| delete_files(command.from_dir, command.n_file, command.from_basename); |
| } |
| |
| using workload_executor_t = std::function<void(Collector*, const Command&)>; |
| |
| std::unordered_map<std::string, workload_executor_t> executors = { |
| {Command::CREATE, create_workload}, {Command::DELETE, delete_workload}, |
| {Command::MOVE, move_workload}, {Command::HARDLINK, hardlink_workload}, |
| {Command::SYMLINK, symlink_workload}, {Command::READDIR, readdir_workload}}; |
| |
| int main(int argc, char** argv) { |
| std::vector<Command> commands; |
| Command command; |
| int opt; |
| |
| while ((opt = getopt(argc, argv, "hvpsw:d:f:t:n:")) != -1) { |
| switch (opt) { |
| case 'h': |
| usage(std::cout, argv[0]); |
| return EXIT_SUCCESS; |
| case 'v': |
| print_version(); |
| return EXIT_SUCCESS; |
| case 'p': |
| print_commands(commands); |
| return EXIT_SUCCESS; |
| case 's': |
| command.drop_state = false; |
| break; |
| case 'w': |
| command.workload = optarg; |
| commands.push_back(command); |
| command.reset(); |
| break; |
| case 'd': |
| case 'f': |
| command.from_dir = optarg; |
| break; |
| case 't': |
| command.to_dir = optarg; |
| break; |
| case 'n': |
| command.n_file = std::stoi(optarg); |
| break; |
| default: |
| usage(std::cerr, argv[0]); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| Collector collector; |
| for (const Command& command : commands) { |
| auto executor = executors.find(command.workload); |
| if (executor == executors.end()) continue; |
| executor->second(&collector, command); |
| } |
| collector.report_metrics(); |
| |
| return EXIT_SUCCESS; |
| } |