xref: /aosp_15_r20/external/zucchini/main_utils.cc (revision a03ca8b91e029cd15055c20c78c2e087c84792e4)
1*a03ca8b9SKrzysztof Kosiński // Copyright 2017 The Chromium Authors. All rights reserved.
2*a03ca8b9SKrzysztof Kosiński // Use of this source code is governed by a BSD-style license that can be
3*a03ca8b9SKrzysztof Kosiński // found in the LICENSE file.
4*a03ca8b9SKrzysztof Kosiński 
5*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/main_utils.h"
6*a03ca8b9SKrzysztof Kosiński 
7*a03ca8b9SKrzysztof Kosiński #include <stddef.h>
8*a03ca8b9SKrzysztof Kosiński 
9*a03ca8b9SKrzysztof Kosiński #include <memory>
10*a03ca8b9SKrzysztof Kosiński #include <ostream>
11*a03ca8b9SKrzysztof Kosiński #include <string>
12*a03ca8b9SKrzysztof Kosiński #include <vector>
13*a03ca8b9SKrzysztof Kosiński 
14*a03ca8b9SKrzysztof Kosiński #include "base/command_line.h"
15*a03ca8b9SKrzysztof Kosiński #include "base/files/file_path.h"
16*a03ca8b9SKrzysztof Kosiński #include "base/files/file_util.h"
17*a03ca8b9SKrzysztof Kosiński #include "base/logging.h"
18*a03ca8b9SKrzysztof Kosiński #include "base/process/process_handle.h"
19*a03ca8b9SKrzysztof Kosiński #include "base/strings/string_number_conversions.h"
20*a03ca8b9SKrzysztof Kosiński #include "base/strings/string_split.h"
21*a03ca8b9SKrzysztof Kosiński #include "base/strings/string_util.h"
22*a03ca8b9SKrzysztof Kosiński #include "base/time/time.h"
23*a03ca8b9SKrzysztof Kosiński #include "build/build_config.h"
24*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/io_utils.h"
25*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/version_info.h"
26*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/zucchini_commands.h"
27*a03ca8b9SKrzysztof Kosiński 
28*a03ca8b9SKrzysztof Kosiński #if defined(OS_WIN)
29*a03ca8b9SKrzysztof Kosiński #include <windows.h>  // This include must come first.
30*a03ca8b9SKrzysztof Kosiński 
31*a03ca8b9SKrzysztof Kosiński #include <psapi.h>
32*a03ca8b9SKrzysztof Kosiński #endif
33*a03ca8b9SKrzysztof Kosiński 
34*a03ca8b9SKrzysztof Kosiński namespace {
35*a03ca8b9SKrzysztof Kosiński 
36*a03ca8b9SKrzysztof Kosiński /******** Command ********/
37*a03ca8b9SKrzysztof Kosiński 
38*a03ca8b9SKrzysztof Kosiński // Specifications for a Zucchini command.
39*a03ca8b9SKrzysztof Kosiński struct Command {
Command__anonf9d0c39c0111::Command40*a03ca8b9SKrzysztof Kosiński   constexpr Command(const char* name_in,
41*a03ca8b9SKrzysztof Kosiński                     const char* usage_in,
42*a03ca8b9SKrzysztof Kosiński                     int num_args_in,
43*a03ca8b9SKrzysztof Kosiński                     CommandFunction command_function_in)
44*a03ca8b9SKrzysztof Kosiński       : name(name_in),
45*a03ca8b9SKrzysztof Kosiński         usage(usage_in),
46*a03ca8b9SKrzysztof Kosiński         num_args(num_args_in),
47*a03ca8b9SKrzysztof Kosiński         command_function(command_function_in) {}
48*a03ca8b9SKrzysztof Kosiński   Command(const Command&) = default;
49*a03ca8b9SKrzysztof Kosiński   ~Command() = default;
50*a03ca8b9SKrzysztof Kosiński 
51*a03ca8b9SKrzysztof Kosiński   // Unique name of command. |-name| is used to select from command-line.
52*a03ca8b9SKrzysztof Kosiński   const char* const name;
53*a03ca8b9SKrzysztof Kosiński 
54*a03ca8b9SKrzysztof Kosiński   // Usage help text of command.
55*a03ca8b9SKrzysztof Kosiński   const char* const usage;
56*a03ca8b9SKrzysztof Kosiński 
57*a03ca8b9SKrzysztof Kosiński   // Number of arguments (assumed to be filenames) used by the command.
58*a03ca8b9SKrzysztof Kosiński   const int num_args;
59*a03ca8b9SKrzysztof Kosiński 
60*a03ca8b9SKrzysztof Kosiński   // Main function to run for the command.
61*a03ca8b9SKrzysztof Kosiński   const CommandFunction command_function;
62*a03ca8b9SKrzysztof Kosiński };
63*a03ca8b9SKrzysztof Kosiński 
64*a03ca8b9SKrzysztof Kosiński /******** List of Zucchini commands ********/
65*a03ca8b9SKrzysztof Kosiński 
66*a03ca8b9SKrzysztof Kosiński constexpr Command kCommands[] = {
67*a03ca8b9SKrzysztof Kosiński     {"gen",
68*a03ca8b9SKrzysztof Kosiński      "-gen <old_file> <new_file> <patch_file> [-raw] [-keep]"
69*a03ca8b9SKrzysztof Kosiński      " [-impose=#+#=#+#,#+#=#+#,...]",
70*a03ca8b9SKrzysztof Kosiński      3, &MainGen},
71*a03ca8b9SKrzysztof Kosiński     {"apply", "-apply <old_file> <patch_file> <new_file> [-keep]", 3,
72*a03ca8b9SKrzysztof Kosiński      &MainApply},
73*a03ca8b9SKrzysztof Kosiński     {"verify", "-verify <patch_file>", 1, &MainVerify},
74*a03ca8b9SKrzysztof Kosiński     {"read", "-read <exe> [-dump]", 1, &MainRead},
75*a03ca8b9SKrzysztof Kosiński     {"detect", "-detect <archive_file>", 1, &MainDetect},
76*a03ca8b9SKrzysztof Kosiński     {"match", "-match <old_file> <new_file> [-impose=#+#=#+#,#+#=#+#,...]", 2,
77*a03ca8b9SKrzysztof Kosiński      &MainMatch},
78*a03ca8b9SKrzysztof Kosiński     {"crc32", "-crc32 <file>", 1, &MainCrc32},
79*a03ca8b9SKrzysztof Kosiński };
80*a03ca8b9SKrzysztof Kosiński 
81*a03ca8b9SKrzysztof Kosiński /******** GetPeakMemoryMetrics ********/
82*a03ca8b9SKrzysztof Kosiński 
83*a03ca8b9SKrzysztof Kosiński #if defined(OS_LINUX) || defined(OS_CHROMEOS)
84*a03ca8b9SKrzysztof Kosiński // Linux does not have an exact mapping to the values used on Windows so use a
85*a03ca8b9SKrzysztof Kosiński // close approximation:
86*a03ca8b9SKrzysztof Kosiński // peak_virtual_memory ~= peak_page_file_usage
87*a03ca8b9SKrzysztof Kosiński // resident_set_size_hwm (high water mark) ~= peak_working_set_size
88*a03ca8b9SKrzysztof Kosiński //
89*a03ca8b9SKrzysztof Kosiński // On failure the input values will be set to 0.
GetPeakMemoryMetrics(size_t * peak_virtual_memory,size_t * resident_set_size_hwm)90*a03ca8b9SKrzysztof Kosiński void GetPeakMemoryMetrics(size_t* peak_virtual_memory,
91*a03ca8b9SKrzysztof Kosiński                           size_t* resident_set_size_hwm) {
92*a03ca8b9SKrzysztof Kosiński   *peak_virtual_memory = 0;
93*a03ca8b9SKrzysztof Kosiński   *resident_set_size_hwm = 0;
94*a03ca8b9SKrzysztof Kosiński   auto status_path =
95*a03ca8b9SKrzysztof Kosiński       base::FilePath("/proc")
96*a03ca8b9SKrzysztof Kosiński           .Append(base::NumberToString(base::GetCurrentProcessHandle()))
97*a03ca8b9SKrzysztof Kosiński           .Append("status");
98*a03ca8b9SKrzysztof Kosiński   std::string contents_string;
99*a03ca8b9SKrzysztof Kosiński   base::ReadFileToString(status_path, &contents_string);
100*a03ca8b9SKrzysztof Kosiński   std::vector<base::StringPiece> lines = base::SplitStringPiece(
101*a03ca8b9SKrzysztof Kosiński       contents_string, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
102*a03ca8b9SKrzysztof Kosiński 
103*a03ca8b9SKrzysztof Kosiński   for (const auto& line : lines) {
104*a03ca8b9SKrzysztof Kosiński     // Tokens should generally be of the form "Metric: <val> kB"
105*a03ca8b9SKrzysztof Kosiński     std::vector<base::StringPiece> tokens = base::SplitStringPiece(
106*a03ca8b9SKrzysztof Kosiński         line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
107*a03ca8b9SKrzysztof Kosiński     if (tokens.size() < 2)
108*a03ca8b9SKrzysztof Kosiński       continue;
109*a03ca8b9SKrzysztof Kosiński 
110*a03ca8b9SKrzysztof Kosiński     if (tokens[0] == "VmPeak:") {
111*a03ca8b9SKrzysztof Kosiński       if (base::StringToSizeT(tokens[1], peak_virtual_memory)) {
112*a03ca8b9SKrzysztof Kosiński         *peak_virtual_memory *= 1024;  // in kiB
113*a03ca8b9SKrzysztof Kosiński         if (*resident_set_size_hwm)
114*a03ca8b9SKrzysztof Kosiński           return;
115*a03ca8b9SKrzysztof Kosiński       }
116*a03ca8b9SKrzysztof Kosiński     } else if (tokens[0] == "VmHWM:") {
117*a03ca8b9SKrzysztof Kosiński       if (base::StringToSizeT(tokens[1], resident_set_size_hwm)) {
118*a03ca8b9SKrzysztof Kosiński         *resident_set_size_hwm *= 1024;  // in kiB
119*a03ca8b9SKrzysztof Kosiński         if (*peak_virtual_memory)
120*a03ca8b9SKrzysztof Kosiński           return;
121*a03ca8b9SKrzysztof Kosiński       }
122*a03ca8b9SKrzysztof Kosiński     }
123*a03ca8b9SKrzysztof Kosiński   }
124*a03ca8b9SKrzysztof Kosiński }
125*a03ca8b9SKrzysztof Kosiński #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS)
126*a03ca8b9SKrzysztof Kosiński 
127*a03ca8b9SKrzysztof Kosiński #if defined(OS_WIN)
128*a03ca8b9SKrzysztof Kosiński // On failure the input values will be set to 0.
GetPeakMemoryMetrics(size_t * peak_page_file_usage,size_t * peak_working_set_size)129*a03ca8b9SKrzysztof Kosiński void GetPeakMemoryMetrics(size_t* peak_page_file_usage,
130*a03ca8b9SKrzysztof Kosiński                           size_t* peak_working_set_size) {
131*a03ca8b9SKrzysztof Kosiński   *peak_page_file_usage = 0;
132*a03ca8b9SKrzysztof Kosiński   *peak_working_set_size = 0;
133*a03ca8b9SKrzysztof Kosiński   PROCESS_MEMORY_COUNTERS pmc;
134*a03ca8b9SKrzysztof Kosiński   if (::GetProcessMemoryInfo(::GetCurrentProcess(), &pmc, sizeof(pmc))) {
135*a03ca8b9SKrzysztof Kosiński     *peak_page_file_usage = pmc.PeakPagefileUsage;
136*a03ca8b9SKrzysztof Kosiński     *peak_working_set_size = pmc.PeakWorkingSetSize;
137*a03ca8b9SKrzysztof Kosiński   }
138*a03ca8b9SKrzysztof Kosiński }
139*a03ca8b9SKrzysztof Kosiński #endif  // defined(OS_WIN)
140*a03ca8b9SKrzysztof Kosiński 
141*a03ca8b9SKrzysztof Kosiński /******** ScopedResourceUsageTracker ********/
142*a03ca8b9SKrzysztof Kosiński 
143*a03ca8b9SKrzysztof Kosiński // A class to track and log system resource usage.
144*a03ca8b9SKrzysztof Kosiński class ScopedResourceUsageTracker {
145*a03ca8b9SKrzysztof Kosiński  public:
146*a03ca8b9SKrzysztof Kosiński   // Initializes states for tracking.
ScopedResourceUsageTracker()147*a03ca8b9SKrzysztof Kosiński   ScopedResourceUsageTracker() {
148*a03ca8b9SKrzysztof Kosiński     start_time_ = base::TimeTicks::Now();
149*a03ca8b9SKrzysztof Kosiński 
150*a03ca8b9SKrzysztof Kosiński #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
151*a03ca8b9SKrzysztof Kosiński     GetPeakMemoryMetrics(&start_peak_page_file_usage_,
152*a03ca8b9SKrzysztof Kosiński                          &start_peak_working_set_size_);
153*a03ca8b9SKrzysztof Kosiński #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
154*a03ca8b9SKrzysztof Kosiński   }
155*a03ca8b9SKrzysztof Kosiński 
156*a03ca8b9SKrzysztof Kosiński   // Computes and prints usage.
~ScopedResourceUsageTracker()157*a03ca8b9SKrzysztof Kosiński   ~ScopedResourceUsageTracker() {
158*a03ca8b9SKrzysztof Kosiński     base::TimeTicks end_time = base::TimeTicks::Now();
159*a03ca8b9SKrzysztof Kosiński 
160*a03ca8b9SKrzysztof Kosiński #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
161*a03ca8b9SKrzysztof Kosiński     size_t cur_peak_page_file_usage = 0;
162*a03ca8b9SKrzysztof Kosiński     size_t cur_peak_working_set_size = 0;
163*a03ca8b9SKrzysztof Kosiński     GetPeakMemoryMetrics(&cur_peak_page_file_usage, &cur_peak_working_set_size);
164*a03ca8b9SKrzysztof Kosiński 
165*a03ca8b9SKrzysztof Kosiński     LOG(INFO) << "Zucchini.PeakPagefileUsage "
166*a03ca8b9SKrzysztof Kosiński               << cur_peak_page_file_usage / 1024 << " KiB";
167*a03ca8b9SKrzysztof Kosiński     LOG(INFO) << "Zucchini.PeakPagefileUsageChange "
168*a03ca8b9SKrzysztof Kosiński               << (cur_peak_page_file_usage - start_peak_page_file_usage_) / 1024
169*a03ca8b9SKrzysztof Kosiński               << " KiB";
170*a03ca8b9SKrzysztof Kosiński     LOG(INFO) << "Zucchini.PeakWorkingSetSize "
171*a03ca8b9SKrzysztof Kosiński               << cur_peak_working_set_size / 1024 << " KiB";
172*a03ca8b9SKrzysztof Kosiński     LOG(INFO) << "Zucchini.PeakWorkingSetSizeChange "
173*a03ca8b9SKrzysztof Kosiński               << (cur_peak_working_set_size - start_peak_working_set_size_) /
174*a03ca8b9SKrzysztof Kosiński                      1024
175*a03ca8b9SKrzysztof Kosiński               << " KiB";
176*a03ca8b9SKrzysztof Kosiński #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
177*a03ca8b9SKrzysztof Kosiński 
178*a03ca8b9SKrzysztof Kosiński     LOG(INFO) << "Zucchini.TotalTime " << (end_time - start_time_).InSecondsF()
179*a03ca8b9SKrzysztof Kosiński               << " s";
180*a03ca8b9SKrzysztof Kosiński   }
181*a03ca8b9SKrzysztof Kosiński 
182*a03ca8b9SKrzysztof Kosiński  private:
183*a03ca8b9SKrzysztof Kosiński   base::TimeTicks start_time_;
184*a03ca8b9SKrzysztof Kosiński #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
185*a03ca8b9SKrzysztof Kosiński   size_t start_peak_page_file_usage_ = 0;
186*a03ca8b9SKrzysztof Kosiński   size_t start_peak_working_set_size_ = 0;
187*a03ca8b9SKrzysztof Kosiński #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
188*a03ca8b9SKrzysztof Kosiński };
189*a03ca8b9SKrzysztof Kosiński 
190*a03ca8b9SKrzysztof Kosiński /******** Helper functions ********/
191*a03ca8b9SKrzysztof Kosiński 
192*a03ca8b9SKrzysztof Kosiński // Translates |command_line| arguments to a vector of base::FilePath (expecting
193*a03ca8b9SKrzysztof Kosiński // exactly |expected_count|). On success, writes the results to |paths| and
194*a03ca8b9SKrzysztof Kosiński // returns true. Otherwise returns false.
CheckAndGetFilePathParams(const base::CommandLine & command_line,size_t expected_count,std::vector<base::FilePath> * paths)195*a03ca8b9SKrzysztof Kosiński bool CheckAndGetFilePathParams(const base::CommandLine& command_line,
196*a03ca8b9SKrzysztof Kosiński                                size_t expected_count,
197*a03ca8b9SKrzysztof Kosiński                                std::vector<base::FilePath>* paths) {
198*a03ca8b9SKrzysztof Kosiński   const base::CommandLine::StringVector& args = command_line.GetArgs();
199*a03ca8b9SKrzysztof Kosiński   if (args.size() != expected_count)
200*a03ca8b9SKrzysztof Kosiński     return false;
201*a03ca8b9SKrzysztof Kosiński 
202*a03ca8b9SKrzysztof Kosiński   paths->clear();
203*a03ca8b9SKrzysztof Kosiński   paths->reserve(args.size());
204*a03ca8b9SKrzysztof Kosiński   for (const auto& arg : args)
205*a03ca8b9SKrzysztof Kosiński     paths->emplace_back(arg);
206*a03ca8b9SKrzysztof Kosiński   return true;
207*a03ca8b9SKrzysztof Kosiński }
208*a03ca8b9SKrzysztof Kosiński 
209*a03ca8b9SKrzysztof Kosiński // Prints main Zucchini usage text.
PrintUsage(std::ostream & err)210*a03ca8b9SKrzysztof Kosiński void PrintUsage(std::ostream& err) {
211*a03ca8b9SKrzysztof Kosiński   err << "Version: " << zucchini::kMajorVersion << "."
212*a03ca8b9SKrzysztof Kosiński       << zucchini::kMinorVersion << std::endl;
213*a03ca8b9SKrzysztof Kosiński   err << "Usage:" << std::endl;
214*a03ca8b9SKrzysztof Kosiński   for (const Command& command : kCommands)
215*a03ca8b9SKrzysztof Kosiński     err << "  zucchini " << command.usage << std::endl;
216*a03ca8b9SKrzysztof Kosiński }
217*a03ca8b9SKrzysztof Kosiński 
218*a03ca8b9SKrzysztof Kosiński }  // namespace
219*a03ca8b9SKrzysztof Kosiński 
220*a03ca8b9SKrzysztof Kosiński /******** Exported Functions ********/
221*a03ca8b9SKrzysztof Kosiński 
RunZucchiniCommand(const base::CommandLine & command_line,std::ostream & out,std::ostream & err)222*a03ca8b9SKrzysztof Kosiński zucchini::status::Code RunZucchiniCommand(const base::CommandLine& command_line,
223*a03ca8b9SKrzysztof Kosiński                                           std::ostream& out,
224*a03ca8b9SKrzysztof Kosiński                                           std::ostream& err) {
225*a03ca8b9SKrzysztof Kosiński   // Look for a command with name that matches input.
226*a03ca8b9SKrzysztof Kosiński   const Command* command_use = nullptr;
227*a03ca8b9SKrzysztof Kosiński   for (const Command& command : kCommands) {
228*a03ca8b9SKrzysztof Kosiński     if (command_line.HasSwitch(command.name)) {
229*a03ca8b9SKrzysztof Kosiński       if (command_use) {        // Too many commands found.
230*a03ca8b9SKrzysztof Kosiński         command_use = nullptr;  // Set to null to flag error.
231*a03ca8b9SKrzysztof Kosiński         break;
232*a03ca8b9SKrzysztof Kosiński       }
233*a03ca8b9SKrzysztof Kosiński       command_use = &command;
234*a03ca8b9SKrzysztof Kosiński     }
235*a03ca8b9SKrzysztof Kosiński   }
236*a03ca8b9SKrzysztof Kosiński 
237*a03ca8b9SKrzysztof Kosiński   // Expect exactly 1 matching command. If 0 or >= 2, print usage and quit.
238*a03ca8b9SKrzysztof Kosiński   if (!command_use) {
239*a03ca8b9SKrzysztof Kosiński     err << "Must have exactly one of:" << std::endl;
240*a03ca8b9SKrzysztof Kosiński     err << "  [";
241*a03ca8b9SKrzysztof Kosiński     zucchini::PrefixSep sep(", ");
242*a03ca8b9SKrzysztof Kosiński     for (const Command& command : kCommands)
243*a03ca8b9SKrzysztof Kosiński       err << sep << "-" << command.name;
244*a03ca8b9SKrzysztof Kosiński     err << "]" << std::endl;
245*a03ca8b9SKrzysztof Kosiński     PrintUsage(err);
246*a03ca8b9SKrzysztof Kosiński     return zucchini::status::kStatusInvalidParam;
247*a03ca8b9SKrzysztof Kosiński   }
248*a03ca8b9SKrzysztof Kosiński 
249*a03ca8b9SKrzysztof Kosiński   // Try to parse filename arguments. On failure, print usage and quit.
250*a03ca8b9SKrzysztof Kosiński   std::vector<base::FilePath> paths;
251*a03ca8b9SKrzysztof Kosiński   if (!CheckAndGetFilePathParams(command_line, command_use->num_args, &paths)) {
252*a03ca8b9SKrzysztof Kosiński     err << command_use->usage << std::endl;
253*a03ca8b9SKrzysztof Kosiński     PrintUsage(err);
254*a03ca8b9SKrzysztof Kosiński     return zucchini::status::kStatusInvalidParam;
255*a03ca8b9SKrzysztof Kosiński   }
256*a03ca8b9SKrzysztof Kosiński 
257*a03ca8b9SKrzysztof Kosiński   ScopedResourceUsageTracker resource_usage_tracker;
258*a03ca8b9SKrzysztof Kosiński   return command_use->command_function({command_line, paths, out, err});
259*a03ca8b9SKrzysztof Kosiński }
260