From 046c854ae9e3b75f4d831d1d214bc003412d3ab8 Mon Sep 17 00:00:00 2001 From: Stefano Duo Date: Mon, 7 Sep 2020 16:30:49 +0000 Subject: [PATCH] bench: Add inodeop_bench benchmark Add new benchmark capable of generating specific inode operations workloads. Currently, it supports create, delete, move, hardlink and symlink. Test: Manual run on cuttlefish and physical device Bug: 165903680 Signed-off-by: Stefano Duo Change-Id: Ia47f259b7ccea5fe1665b272c3cbc9ec1bf2eb56 --- bench/inodeop_bench/Android.bp | 19 ++ bench/inodeop_bench/OWNERS | 3 + bench/inodeop_bench/inodeop_bench.cpp | 403 ++++++++++++++++++++++++++ 3 files changed, 425 insertions(+) create mode 100644 bench/inodeop_bench/Android.bp create mode 100644 bench/inodeop_bench/OWNERS create mode 100644 bench/inodeop_bench/inodeop_bench.cpp diff --git a/bench/inodeop_bench/Android.bp b/bench/inodeop_bench/Android.bp new file mode 100644 index 0000000..a01ddd1 --- /dev/null +++ b/bench/inodeop_bench/Android.bp @@ -0,0 +1,19 @@ +/* + * 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. + */ +cc_binary { + name: "inodeop_bench", + srcs: ["inodeop_bench.cpp"], +} diff --git a/bench/inodeop_bench/OWNERS b/bench/inodeop_bench/OWNERS new file mode 100644 index 0000000..3ced4a1 --- /dev/null +++ b/bench/inodeop_bench/OWNERS @@ -0,0 +1,3 @@ +balsini@google.com +stefanoduo@google.com +zezeozue@google.com diff --git a/bench/inodeop_bench/inodeop_bench.cpp b/bench/inodeop_bench/inodeop_bench.cpp new file mode 100644 index 0000000..8ff544f --- /dev/null +++ b/bench/inodeop_bench/inodeop_bench.cpp @@ -0,0 +1,403 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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; + using time_unit = std::chrono::duration; + + 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 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(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& 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; + +std::unordered_map 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 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; +}