// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/zucchini/main_utils.h" #include #include #include #include #include #include "base/command_line.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/process/process_handle.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/time/time.h" #include "build/build_config.h" #include "components/zucchini/io_utils.h" #include "components/zucchini/version_info.h" #include "components/zucchini/zucchini_commands.h" #if BUILDFLAG(IS_WIN) #include // This include must come first. #include #endif namespace { /******** Command ********/ // Specifications for a Zucchini command. struct Command { constexpr Command(const char* name_in, const char* usage_in, int num_args_in, CommandFunction command_function_in) : name(name_in), usage(usage_in), num_args(num_args_in), command_function(command_function_in) {} Command(const Command&) = default; ~Command() = default; // Unique name of command. |-name| is used to select from command-line. const char* const name; // Usage help text of command. const char* const usage; // Number of arguments (assumed to be filenames) used by the command. const int num_args; // Main function to run for the command. const CommandFunction command_function; }; /******** List of Zucchini commands ********/ constexpr Command kCommands[] = { {"gen", "-gen [-raw] [-keep]" " [-impose=#+#=#+#,#+#=#+#,...]", 3, &MainGen}, {"apply", "-apply [-keep]", 3, &MainApply}, {"verify", "-verify ", 1, &MainVerify}, {"read", "-read [-dump]", 1, &MainRead}, {"detect", "-detect ", 1, &MainDetect}, {"match", "-match [-impose=#+#=#+#,#+#=#+#,...]", 2, &MainMatch}, {"crc32", "-crc32 ", 1, &MainCrc32}, }; /******** GetPeakMemoryMetrics ********/ #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) // Linux does not have an exact mapping to the values used on Windows so use a // close approximation: // peak_virtual_memory ~= peak_page_file_usage // resident_set_size_hwm (high water mark) ~= peak_working_set_size // // On failure the input values will be set to 0. void GetPeakMemoryMetrics(size_t* peak_virtual_memory, size_t* resident_set_size_hwm) { *peak_virtual_memory = 0; *resident_set_size_hwm = 0; auto status_path = base::FilePath("/proc") .Append(base::NumberToString(base::GetCurrentProcessHandle())) .Append("status"); std::string contents_string; base::ReadFileToString(status_path, &contents_string); std::vector lines = base::SplitStringPiece( contents_string, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); for (const auto& line : lines) { // Tokens should generally be of the form "Metric: kB" std::vector tokens = base::SplitStringPiece( line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); if (tokens.size() < 2) continue; if (tokens[0] == "VmPeak:") { if (base::StringToSizeT(tokens[1], peak_virtual_memory)) { *peak_virtual_memory *= 1024; // in kiB if (*resident_set_size_hwm) return; } } else if (tokens[0] == "VmHWM:") { if (base::StringToSizeT(tokens[1], resident_set_size_hwm)) { *resident_set_size_hwm *= 1024; // in kiB if (*peak_virtual_memory) return; } } } } #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_WIN) // On failure the input values will be set to 0. void GetPeakMemoryMetrics(size_t* peak_page_file_usage, size_t* peak_working_set_size) { *peak_page_file_usage = 0; *peak_working_set_size = 0; PROCESS_MEMORY_COUNTERS pmc; if (::GetProcessMemoryInfo(::GetCurrentProcess(), &pmc, sizeof(pmc))) { *peak_page_file_usage = pmc.PeakPagefileUsage; *peak_working_set_size = pmc.PeakWorkingSetSize; } } #endif // BUILDFLAG(IS_WIN) /******** ScopedResourceUsageTracker ********/ // A class to track and log system resource usage. class ScopedResourceUsageTracker { public: // Initializes states for tracking. ScopedResourceUsageTracker() { start_time_ = base::TimeTicks::Now(); #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) GetPeakMemoryMetrics(&start_peak_page_file_usage_, &start_peak_working_set_size_); #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) } // Computes and prints usage. ~ScopedResourceUsageTracker() { base::TimeTicks end_time = base::TimeTicks::Now(); #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) size_t cur_peak_page_file_usage = 0; size_t cur_peak_working_set_size = 0; GetPeakMemoryMetrics(&cur_peak_page_file_usage, &cur_peak_working_set_size); LOG(INFO) << "Zucchini.PeakPagefileUsage " << cur_peak_page_file_usage / 1024 << " KiB"; LOG(INFO) << "Zucchini.PeakPagefileUsageChange " << (cur_peak_page_file_usage - start_peak_page_file_usage_) / 1024 << " KiB"; LOG(INFO) << "Zucchini.PeakWorkingSetSize " << cur_peak_working_set_size / 1024 << " KiB"; LOG(INFO) << "Zucchini.PeakWorkingSetSizeChange " << (cur_peak_working_set_size - start_peak_working_set_size_) / 1024 << " KiB"; #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) LOG(INFO) << "Zucchini.TotalTime " << (end_time - start_time_).InSecondsF() << " s"; } private: base::TimeTicks start_time_; #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) size_t start_peak_page_file_usage_ = 0; size_t start_peak_working_set_size_ = 0; #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) }; /******** Helper functions ********/ // Translates |command_line| arguments to a vector of base::FilePath (expecting // exactly |expected_count|). On success, writes the results to |paths| and // returns true. Otherwise returns false. bool CheckAndGetFilePathParams(const base::CommandLine& command_line, size_t expected_count, std::vector* paths) { const base::CommandLine::StringVector& args = command_line.GetArgs(); if (args.size() != expected_count) return false; paths->clear(); paths->reserve(args.size()); for (const auto& arg : args) paths->emplace_back(arg); return true; } // Prints main Zucchini usage text. void PrintUsage(std::ostream& err) { err << "Version: " << zucchini::kMajorVersion << "." << zucchini::kMinorVersion << std::endl; err << "Usage:" << std::endl; for (const Command& command : kCommands) err << " zucchini " << command.usage << std::endl; } } // namespace /******** Exported Functions ********/ zucchini::status::Code RunZucchiniCommand(const base::CommandLine& command_line, std::ostream& out, std::ostream& err) { // Look for a command with name that matches input. const Command* command_use = nullptr; for (const Command& command : kCommands) { if (command_line.HasSwitch(command.name)) { if (command_use) { // Too many commands found. command_use = nullptr; // Set to null to flag error. break; } command_use = &command; } } // Expect exactly 1 matching command. If 0 or >= 2, print usage and quit. if (!command_use) { err << "Must have exactly one of:" << std::endl; err << " ["; zucchini::PrefixSep sep(", "); for (const Command& command : kCommands) err << sep << "-" << command.name; err << "]" << std::endl; PrintUsage(err); return zucchini::status::kStatusInvalidParam; } // Try to parse filename arguments. On failure, print usage and quit. std::vector paths; if (!CheckAndGetFilePathParams(command_line, command_use->num_args, &paths)) { err << command_use->usage << std::endl; PrintUsage(err); return zucchini::status::kStatusInvalidParam; } ScopedResourceUsageTracker resource_usage_tracker; return command_use->command_function({command_line, paths, out, err}); }