1 /*
2  * Copyright (C) 2022 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 
17 #include <sys/stat.h>
18 
19 #include <android-base/properties.h>
20 #include <android-base/stringprintf.h>
21 #include <android-base/strings.h>
22 
23 #include "cmd_record_impl.h"
24 #include "command.h"
25 #include "utils.h"
26 #include "workload.h"
27 
28 namespace simpleperf {
29 namespace {
30 
31 enum class Action {
32   NONE,
33   ENABLE,
34   DISABLE,
35   RECORD,
36 };
37 
38 const std::string RECORD_OUT_DIR = "/data/simpleperf_boot_data";
39 
40 class BootRecordCommand : public Command {
41  public:
BootRecordCommand()42   BootRecordCommand()
43       : Command(
44             "boot-record", "record at boot time",
45             // clang-format off
46 "Usage: simpleperf boot-record [options]\n"
47 "    Record boot-time profiles. Only supported on userdebug/eng build. The record file will be\n"
48 "    stored in /data/simpleperf_boot_data.\n"
49 "--enable <record_options>  Enable boot time recording. Record options are options passed\n"
50 "                           to simpleperf record cmd, like \"-a -g --duration 10\".\n"
51 "--disable                  Disable record at boot time.\n"
52 #if 0
53 // The following option is only used internally.
54 "--record <record_options>  Record with record options.\n"
55 #endif
56             // clang-format on
57         ) {
58   }
59 
60   bool Run(const std::vector<std::string>& args);
61 
62  private:
63   bool ParseOptions(const std::vector<std::string>& args);
64   bool SetRecordOptions(const std::string& record_options);
65   bool CheckRecordOptions(const std::string& record_options);
66   bool CreateOutDir();
67   bool Record();
68   std::string GetDefaultOutputFilename();
69 
70   Action action_ = Action::NONE;
71   std::string record_options_;
72 };
73 
Run(const std::vector<std::string> & args)74 bool BootRecordCommand::Run(const std::vector<std::string>& args) {
75   if (!ParseOptions(args)) {
76     return false;
77   }
78   if (action_ == Action::ENABLE) {
79     if (!CheckRecordOptions(record_options_) || !SetRecordOptions(record_options_) ||
80         !CreateOutDir()) {
81       return false;
82     }
83     LOG(INFO) << "After boot, boot profile will be stored in " << RECORD_OUT_DIR;
84     return true;
85   }
86   if (action_ == Action::DISABLE) {
87     return SetRecordOptions("");
88   }
89   if (action_ == Action::RECORD) {
90     return Record();
91   }
92   return true;
93 }
94 
ParseOptions(const std::vector<std::string> & args)95 bool BootRecordCommand::ParseOptions(const std::vector<std::string>& args) {
96   const OptionFormatMap option_formats = {
97       {"--enable", {OptionValueType::STRING, OptionType::SINGLE}},
98       {"--disable", {OptionValueType::NONE, OptionType::SINGLE}},
99       {"--record", {OptionValueType::STRING, OptionType::SINGLE}},
100   };
101   OptionValueMap options;
102   std::vector<std::pair<OptionName, OptionValue>> ordered_options;
103   std::vector<std::string> non_option_args;
104   if (!PreprocessOptions(args, option_formats, &options, &ordered_options, &non_option_args)) {
105     return false;
106   }
107   if (auto value = options.PullValue("--enable"); value) {
108     action_ = Action::ENABLE;
109     record_options_ = *value->str_value;
110   } else if (options.PullBoolValue("--disable")) {
111     action_ = Action::DISABLE;
112   } else if (auto value = options.PullValue("--record"); value) {
113     action_ = Action::RECORD;
114     record_options_ = *value->str_value;
115   }
116   return true;
117 }
118 
SetRecordOptions(const std::string & record_options)119 bool BootRecordCommand::SetRecordOptions(const std::string& record_options) {
120   const std::string prop_name = "persist.simpleperf.boot_record";
121   if (!android::base::SetProperty(prop_name, record_options)) {
122     LOG(ERROR) << "Failed to SetProperty " << prop_name << " to \"" << record_options << "\"";
123     return false;
124   }
125   return true;
126 }
127 
CheckRecordOptions(const std::string & record_options)128 bool BootRecordCommand::CheckRecordOptions(const std::string& record_options) {
129   std::vector<std::string> args = android::base::Split(record_options, " ");
130 
131   OptionValueMap options;
132   std::vector<std::pair<OptionName, OptionValue>> ordered_options;
133   std::vector<std::string> non_option_args;
134   if (!PreprocessOptions(args, GetRecordCmdOptionFormats(), &options, &ordered_options,
135                          &non_option_args)) {
136     LOG(ERROR) << "Invalid record options.";
137     return false;
138   }
139   if (!non_option_args.empty()) {
140     LOG(ERROR) << "Running child command isn't allowed";
141     return false;
142   }
143   if (auto value = options.PullValue("-o"); value) {
144     LOG(ERROR) << "-o option isn't allowed. The output file is stored in " << RECORD_OUT_DIR;
145     return false;
146   }
147   return true;
148 }
149 
CreateOutDir()150 bool BootRecordCommand::CreateOutDir() {
151   if (!IsDir(RECORD_OUT_DIR)) {
152     if (mkdir(RECORD_OUT_DIR.c_str(), 0775) != 0) {
153       PLOG(ERROR) << "failed to create dir " << RECORD_OUT_DIR;
154       return false;
155     }
156     return Workload::RunCmd(
157         {"chcon", "u:object_r:simpleperf_boot_data_file:s0", "/data/simpleperf_boot_data"});
158   }
159   return true;
160 }
161 
Record()162 bool BootRecordCommand::Record() {
163   if (!CheckRecordOptions(record_options_)) {
164     return false;
165   }
166 
167   std::vector<std::string> args = android::base::Split(record_options_, " ");
168   std::string output_file = RECORD_OUT_DIR + "/" + GetDefaultOutputFilename();
169   args.emplace_back("-o");
170   args.emplace_back(output_file);
171   std::unique_ptr<Command> record_cmd = CreateCommandInstance("record");
172   CHECK(record_cmd != nullptr);
173   return record_cmd->Run(args);
174 }
175 
GetDefaultOutputFilename()176 std::string BootRecordCommand::GetDefaultOutputFilename() {
177   time_t t = time(nullptr);
178   struct tm tm;
179   if (localtime_r(&t, &tm) != &tm) {
180     return "perf.data";
181   }
182   return android::base::StringPrintf("perf-%04d%02d%02d-%02d-%02d-%02d.data", tm.tm_year + 1900,
183                                      tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
184 }
185 
186 }  // namespace
187 
RegisterBootRecordCommand()188 void RegisterBootRecordCommand() {
189   RegisterCommand("boot-record", [] { return std::unique_ptr<Command>(new BootRecordCommand); });
190 }
191 
192 }  // namespace simpleperf
193