• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 <stdio.h>
18 
19 #include <memory>
20 #include <regex>
21 #include <string>
22 #include <thread>
23 #include <vector>
24 
25 #include <android-base/file.h>
26 #include <android-base/logging.h>
27 #include <android-base/parseint.h>
28 #include <android-base/properties.h>
29 #include <android-base/strings.h>
30 #include <android-base/unique_fd.h>
31 #include <ziparchive/zip_writer.h>
32 
33 #include "cmd_api_impl.h"
34 #include "command.h"
35 #include "environment.h"
36 #include "event_type.h"
37 #include "utils.h"
38 #include "workload.h"
39 
40 namespace simpleperf {
41 namespace {
42 
43 const std::string SIMPLEPERF_DATA_DIR = "simpleperf_data";
44 
45 class PrepareCommand : public Command {
46  public:
PrepareCommand()47   PrepareCommand()
48       : Command("api-prepare", "Prepare recording via app api",
49                 // clang-format off
50 "Usage: simpleperf api-prepare [options]\n"
51 "--app <package_name>    the android application to record via app api\n"
52 "--days <days>           By default, the recording permission is reset after device reboot.\n"
53 "                        But on Android >= 13, we can use this option to set how long we want\n"
54 "                        the permission to last. It can last after device reboot.\n"
55                 // clang-format on
56         ) {}
57   bool Run(const std::vector<std::string>& args);
58 
59  private:
60   bool ParseOptions(const std::vector<std::string>& args);
61   std::optional<uint32_t> GetAppUid();
62 
63   std::string app_name_;
64   uint64_t days_ = 0;
65 };
66 
Run(const std::vector<std::string> & args)67 bool PrepareCommand::Run(const std::vector<std::string>& args) {
68   if (!ParseOptions(args)) {
69     return false;
70   }
71   // Enable profiling.
72   if (GetAndroidVersion() >= 13 && !app_name_.empty() && days_ != 0) {
73     // Enable app recording via persist properties.
74     uint64_t duration_in_sec;
75     uint64_t expiration_time;
76     if (__builtin_mul_overflow(days_, 24 * 3600, &duration_in_sec) ||
77         __builtin_add_overflow(time(nullptr), duration_in_sec, &expiration_time)) {
78       expiration_time = UINT64_MAX;
79     }
80     std::optional<uint32_t> uid = GetAppUid();
81     if (!uid) {
82       return false;
83     }
84     if (!android::base::SetProperty("persist.simpleperf.profile_app_uid",
85                                     std::to_string(uid.value())) ||
86         !android::base::SetProperty("persist.simpleperf.profile_app_expiration_time",
87                                     std::to_string(expiration_time))) {
88       LOG(ERROR) << "failed to set system properties";
89       return false;
90     }
91   } else {
92     // Enable app recording via security.perf_harden.
93     if (!CheckPerfEventLimit()) {
94       return false;
95     }
96   }
97 
98   // Create tracepoint_events file.
99   return EventTypeManager::Instance().WriteTracepointsToFile("/data/local/tmp/tracepoint_events");
100 }
101 
ParseOptions(const std::vector<std::string> & args)102 bool PrepareCommand::ParseOptions(const std::vector<std::string>& args) {
103   OptionValueMap options;
104   std::vector<std::pair<OptionName, OptionValue>> ordered_options;
105   static const OptionFormatMap option_formats = {
106       {"--app", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
107       {"--days", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
108   };
109   if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
110     return false;
111   }
112 
113   if (auto value = options.PullValue("--app"); value) {
114     app_name_ = *value->str_value;
115   }
116   if (!options.PullUintValue("--days", &days_)) {
117     return false;
118   }
119   return true;
120 }
121 
GetAppUid()122 std::optional<uint32_t> PrepareCommand::GetAppUid() {
123   std::unique_ptr<FILE, decltype(&pclose)> fp(popen("pm list packages -U", "re"), pclose);
124   std::string content;
125   if (!fp || !android::base::ReadFdToString(fileno(fp.get()), &content)) {
126     PLOG(ERROR) << "failed to run `pm list packages -U`";
127     return std::nullopt;
128   }
129   std::regex re(R"(package:([\w\.]+)\s+uid:(\d+))");
130   std::sregex_iterator match_it(content.begin(), content.end(), re);
131   std::sregex_iterator match_end;
132   while (match_it != match_end) {
133     std::smatch match = *match_it++;
134     std::string name = match.str(1);
135     uint32_t uid;
136     if (!android::base::ParseUint(match.str(2), &uid)) {
137       continue;
138     }
139     if (name == app_name_) {
140       return uid;
141     }
142   }
143   LOG(ERROR) << "failed to find package " << app_name_;
144   return std::nullopt;
145 }
146 
147 class CollectCommand : public Command {
148  public:
CollectCommand()149   CollectCommand()
150       : Command("api-collect", "Collect recording data generated by app api",
151                 // clang-format off
152 "Usage: simpleperf api-collect [options]\n"
153 "--app <package_name>    the android application having recording data\n"
154 "-o record_zipfile_path  the path to store recording data\n"
155 "                        Default is simpleperf_data.zip.\n"
156 #if 0
157 // Below options are only used internally and shouldn't be visible to the public.
158 "--in-app               We are already running in the app's context.\n"
159 "--out-fd <fd>          Write output to a file descriptor.\n"
160 "--stop-signal-fd <fd>  Stop recording when fd is readable.\n"
161 #endif
162                 // clang-format on
163         ) {
164   }
165   bool Run(const std::vector<std::string>& args);
166 
167  private:
168   bool ParseOptions(const std::vector<std::string>& args);
169   void HandleStopSignal();
170   bool CollectRecordingData();
171   bool RemoveRecordingData();
172 
173   std::string app_name_;
174   std::string output_filepath_ = "simpleperf_data.zip";
175   bool in_app_context_ = false;
176   android::base::unique_fd out_fd_;
177   android::base::unique_fd stop_signal_fd_;
178 };
179 
Run(const std::vector<std::string> & args)180 bool CollectCommand::Run(const std::vector<std::string>& args) {
181   if (!ParseOptions(args)) {
182     return false;
183   }
184   if (in_app_context_) {
185     HandleStopSignal();
186     return CollectRecordingData() && RemoveRecordingData();
187   }
188   return RunInAppContext(app_name_, Name(), args, 0, output_filepath_, false);
189 }
190 
ParseOptions(const std::vector<std::string> & args)191 bool CollectCommand::ParseOptions(const std::vector<std::string>& args) {
192   OptionValueMap options;
193   std::vector<std::pair<OptionName, OptionValue>> ordered_options;
194   if (!PreprocessOptions(args, GetApiCollectCmdOptionFormats(), &options, &ordered_options,
195                          nullptr)) {
196     return false;
197   }
198 
199   if (auto value = options.PullValue("--app"); value) {
200     app_name_ = *value->str_value;
201   }
202   in_app_context_ = options.PullBoolValue("--in-app");
203 
204   if (auto value = options.PullValue("-o"); value) {
205     output_filepath_ = *value->str_value;
206   }
207   if (auto value = options.PullValue("--out-fd"); value) {
208     out_fd_.reset(static_cast<int>(value->uint_value));
209   }
210   if (auto value = options.PullValue("--stop-signal-fd"); value) {
211     stop_signal_fd_.reset(static_cast<int>(value->uint_value));
212   }
213 
214   CHECK(options.values.empty());
215   CHECK(ordered_options.empty());
216   if (!in_app_context_) {
217     if (app_name_.empty()) {
218       LOG(ERROR) << "--app is missing";
219       return false;
220     }
221   }
222   return true;
223 }
224 
HandleStopSignal()225 void CollectCommand::HandleStopSignal() {
226   int fd = stop_signal_fd_.release();
227   std::thread thread([fd]() {
228     char c;
229     static_cast<void>(read(fd, &c, 1));
230     exit(1);
231   });
232   thread.detach();
233 }
234 
CollectRecordingData()235 bool CollectCommand::CollectRecordingData() {
236   std::unique_ptr<FILE, decltype(&fclose)> fp(android::base::Fdopen(std::move(out_fd_), "w"),
237                                               fclose);
238   if (fp == nullptr) {
239     PLOG(ERROR) << "failed to call fdopen";
240     return false;
241   }
242   std::vector<char> buffer(64 * 1024);
243   ZipWriter zip_writer(fp.get());
244   for (const auto& name : GetEntriesInDir(SIMPLEPERF_DATA_DIR)) {
245     // No need to collect temporary files.
246     const std::string path = SIMPLEPERF_DATA_DIR + "/" + name;
247     if (android::base::StartsWith(name, "TemporaryFile-") || !IsRegularFile(path)) {
248       continue;
249     }
250     int result = zip_writer.StartEntry(name.c_str(), ZipWriter::kCompress);
251     if (result != 0) {
252       LOG(ERROR) << "failed to start zip entry " << name << ": "
253                  << zip_writer.ErrorCodeString(result);
254       return false;
255     }
256     android::base::unique_fd in_fd(FileHelper::OpenReadOnly(path));
257     if (in_fd == -1) {
258       PLOG(ERROR) << "failed to open " << path;
259       return false;
260     }
261     while (true) {
262       ssize_t nread = TEMP_FAILURE_RETRY(read(in_fd, buffer.data(), buffer.size()));
263       if (nread < 0) {
264         PLOG(ERROR) << "failed to read " << path;
265         return false;
266       }
267       if (nread == 0) {
268         break;
269       }
270       result = zip_writer.WriteBytes(buffer.data(), nread);
271       if (result != 0) {
272         LOG(ERROR) << "failed to write zip entry " << name << ": "
273                    << zip_writer.ErrorCodeString(result);
274         return false;
275       }
276     }
277     result = zip_writer.FinishEntry();
278     if (result != 0) {
279       LOG(ERROR) << "failed to finish zip entry " << name << ": "
280                  << zip_writer.ErrorCodeString(result);
281       return false;
282     }
283   }
284   int result = zip_writer.Finish();
285   if (result != 0) {
286     LOG(ERROR) << "failed to finish zip writer: " << zip_writer.ErrorCodeString(result);
287     return false;
288   }
289   return true;
290 }
291 
RemoveRecordingData()292 bool CollectCommand::RemoveRecordingData() {
293   return Workload::RunCmd({"rm", "-rf", SIMPLEPERF_DATA_DIR});
294 }
295 }  // namespace
296 
RegisterAPICommands()297 void RegisterAPICommands() {
298   RegisterCommand("api-prepare", [] { return std::unique_ptr<Command>(new PrepareCommand()); });
299   RegisterCommand("api-collect", [] { return std::unique_ptr<Command>(new CollectCommand()); });
300 }
301 
302 }  // namespace simpleperf
303