blob: f3ce2bb9119b3cb8668b299f616d0bb84070ee84 [file] [log] [blame]
Yabin Cui1befe4f2019-02-25 15:22:43 -08001/*
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 <string>
21#include <thread>
22#include <vector>
23
24#include <android-base/file.h>
25#include <android-base/logging.h>
26#include <android-base/parseint.h>
Yabin Cui158a5fd2021-11-03 12:08:42 -070027#include <android-base/properties.h>
Yabin Cui1befe4f2019-02-25 15:22:43 -080028#include <android-base/strings.h>
29#include <android-base/unique_fd.h>
30#include <ziparchive/zip_writer.h>
31
Yabin Cuif00f4fc2022-11-23 15:15:30 -080032#include "RegEx.h"
Yabin Cui6f094672020-07-22 14:50:35 -070033#include "cmd_api_impl.h"
Yabin Cui1befe4f2019-02-25 15:22:43 -080034#include "command.h"
Yabin Cui1befe4f2019-02-25 15:22:43 -080035#include "environment.h"
Thiébaud Weksteen4848ee02020-10-23 16:06:59 +020036#include "event_type.h"
Yabin Cui1befe4f2019-02-25 15:22:43 -080037#include "utils.h"
38#include "workload.h"
39
Yabin Cuifaa7b922021-01-11 17:35:57 -080040namespace simpleperf {
Yabin Cui1befe4f2019-02-25 15:22:43 -080041namespace {
Yabin Cuifaa7b922021-01-11 17:35:57 -080042
Yabin Cui1befe4f2019-02-25 15:22:43 -080043const std::string SIMPLEPERF_DATA_DIR = "simpleperf_data";
44
45class PrepareCommand : public Command {
46 public:
47 PrepareCommand()
Yabin Cui158a5fd2021-11-03 12:08:42 -070048 : 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 ) {}
Yabin Cui1befe4f2019-02-25 15:22:43 -080057 bool Run(const std::vector<std::string>& args);
Yabin Cui158a5fd2021-11-03 12:08:42 -070058
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;
Yabin Cui1befe4f2019-02-25 15:22:43 -080065};
66
Yabin Cui158a5fd2021-11-03 12:08:42 -070067bool PrepareCommand::Run(const std::vector<std::string>& args) {
68 if (!ParseOptions(args)) {
Yabin Cui1befe4f2019-02-25 15:22:43 -080069 return false;
70 }
Yabin Cui158a5fd2021-11-03 12:08:42 -070071 // 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
Yabin Cui1befe4f2019-02-25 15:22:43 -080098 // Create tracepoint_events file.
Yabin Cui16a6ace2020-10-01 14:56:32 -070099 return EventTypeManager::Instance().WriteTracepointsToFile("/data/local/tmp/tracepoint_events");
Yabin Cui1befe4f2019-02-25 15:22:43 -0800100}
101
Yabin Cui158a5fd2021-11-03 12:08:42 -0700102bool 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
122std::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 }
Yabin Cuif00f4fc2022-11-23 15:15:30 -0800129 auto re = RegEx::Create(R"(package:([\w\.]+)\s+uid:(\d+))");
130 auto match = re->SearchAll(content);
131 while (match->IsValid()) {
132 std::string name = match->GetField(1);
Yabin Cui158a5fd2021-11-03 12:08:42 -0700133 uint32_t uid;
Yabin Cuif00f4fc2022-11-23 15:15:30 -0800134 if (name == app_name_ && android::base::ParseUint(match->GetField(2), &uid)) {
Yabin Cui158a5fd2021-11-03 12:08:42 -0700135 return uid;
136 }
Yabin Cuif00f4fc2022-11-23 15:15:30 -0800137 match->MoveToNextMatch();
Yabin Cui158a5fd2021-11-03 12:08:42 -0700138 }
139 LOG(ERROR) << "failed to find package " << app_name_;
140 return std::nullopt;
141}
142
Yabin Cui1befe4f2019-02-25 15:22:43 -0800143class CollectCommand : public Command {
144 public:
145 CollectCommand()
146 : Command("api-collect", "Collect recording data generated by app api",
147 // clang-format off
148"Usage: simpleperf api-collect [options]\n"
149"--app <package_name> the android application having recording data\n"
150"-o record_zipfile_path the path to store recording data\n"
151" Default is simpleperf_data.zip.\n"
152#if 0
153// Below options are only used internally and shouldn't be visible to the public.
154"--in-app We are already running in the app's context.\n"
155"--out-fd <fd> Write output to a file descriptor.\n"
156"--stop-signal-fd <fd> Stop recording when fd is readable.\n"
157#endif
158 // clang-format on
Thiébaud Weksteen4848ee02020-10-23 16:06:59 +0200159 ) {
160 }
Yabin Cui1befe4f2019-02-25 15:22:43 -0800161 bool Run(const std::vector<std::string>& args);
162
163 private:
164 bool ParseOptions(const std::vector<std::string>& args);
165 void HandleStopSignal();
166 bool CollectRecordingData();
167 bool RemoveRecordingData();
168
169 std::string app_name_;
170 std::string output_filepath_ = "simpleperf_data.zip";
171 bool in_app_context_ = false;
172 android::base::unique_fd out_fd_;
173 android::base::unique_fd stop_signal_fd_;
174};
175
176bool CollectCommand::Run(const std::vector<std::string>& args) {
177 if (!ParseOptions(args)) {
178 return false;
179 }
180 if (in_app_context_) {
181 HandleStopSignal();
182 return CollectRecordingData() && RemoveRecordingData();
183 }
184 return RunInAppContext(app_name_, Name(), args, 0, output_filepath_, false);
185}
186
187bool CollectCommand::ParseOptions(const std::vector<std::string>& args) {
Yabin Cui6f094672020-07-22 14:50:35 -0700188 OptionValueMap options;
189 std::vector<std::pair<OptionName, OptionValue>> ordered_options;
Yabin Cui6f094672020-07-22 14:50:35 -0700190 if (!PreprocessOptions(args, GetApiCollectCmdOptionFormats(), &options, &ordered_options,
Yabin Cuie09cb9f2020-11-03 09:24:48 -0800191 nullptr)) {
Yabin Cui6f094672020-07-22 14:50:35 -0700192 return false;
Yabin Cui1befe4f2019-02-25 15:22:43 -0800193 }
Yabin Cui6f094672020-07-22 14:50:35 -0700194
195 if (auto value = options.PullValue("--app"); value) {
196 app_name_ = *value->str_value;
197 }
198 in_app_context_ = options.PullBoolValue("--in-app");
199
200 if (auto value = options.PullValue("-o"); value) {
201 output_filepath_ = *value->str_value;
202 }
203 if (auto value = options.PullValue("--out-fd"); value) {
204 out_fd_.reset(static_cast<int>(value->uint_value));
205 }
206 if (auto value = options.PullValue("--stop-signal-fd"); value) {
207 stop_signal_fd_.reset(static_cast<int>(value->uint_value));
208 }
209
210 CHECK(options.values.empty());
211 CHECK(ordered_options.empty());
Yabin Cui1befe4f2019-02-25 15:22:43 -0800212 if (!in_app_context_) {
213 if (app_name_.empty()) {
214 LOG(ERROR) << "--app is missing";
215 return false;
216 }
217 }
218 return true;
219}
220
221void CollectCommand::HandleStopSignal() {
222 int fd = stop_signal_fd_.release();
223 std::thread thread([fd]() {
224 char c;
225 static_cast<void>(read(fd, &c, 1));
226 exit(1);
227 });
228 thread.detach();
229}
230
231bool CollectCommand::CollectRecordingData() {
Thiébaud Weksteen4848ee02020-10-23 16:06:59 +0200232 std::unique_ptr<FILE, decltype(&fclose)> fp(android::base::Fdopen(std::move(out_fd_), "w"),
233 fclose);
Yabin Cui1befe4f2019-02-25 15:22:43 -0800234 if (fp == nullptr) {
235 PLOG(ERROR) << "failed to call fdopen";
236 return false;
237 }
238 std::vector<char> buffer(64 * 1024);
239 ZipWriter zip_writer(fp.get());
240 for (const auto& name : GetEntriesInDir(SIMPLEPERF_DATA_DIR)) {
241 // No need to collect temporary files.
242 const std::string path = SIMPLEPERF_DATA_DIR + "/" + name;
243 if (android::base::StartsWith(name, "TemporaryFile-") || !IsRegularFile(path)) {
244 continue;
245 }
246 int result = zip_writer.StartEntry(name.c_str(), ZipWriter::kCompress);
247 if (result != 0) {
248 LOG(ERROR) << "failed to start zip entry " << name << ": "
249 << zip_writer.ErrorCodeString(result);
250 return false;
251 }
252 android::base::unique_fd in_fd(FileHelper::OpenReadOnly(path));
253 if (in_fd == -1) {
254 PLOG(ERROR) << "failed to open " << path;
255 return false;
256 }
257 while (true) {
258 ssize_t nread = TEMP_FAILURE_RETRY(read(in_fd, buffer.data(), buffer.size()));
259 if (nread < 0) {
260 PLOG(ERROR) << "failed to read " << path;
261 return false;
262 }
263 if (nread == 0) {
264 break;
265 }
266 result = zip_writer.WriteBytes(buffer.data(), nread);
267 if (result != 0) {
268 LOG(ERROR) << "failed to write zip entry " << name << ": "
269 << zip_writer.ErrorCodeString(result);
270 return false;
271 }
272 }
273 result = zip_writer.FinishEntry();
274 if (result != 0) {
275 LOG(ERROR) << "failed to finish zip entry " << name << ": "
276 << zip_writer.ErrorCodeString(result);
277 return false;
278 }
279 }
280 int result = zip_writer.Finish();
281 if (result != 0) {
282 LOG(ERROR) << "failed to finish zip writer: " << zip_writer.ErrorCodeString(result);
283 return false;
284 }
285 return true;
286}
287
288bool CollectCommand::RemoveRecordingData() {
289 return Workload::RunCmd({"rm", "-rf", SIMPLEPERF_DATA_DIR});
290}
291} // namespace
292
293void RegisterAPICommands() {
Thiébaud Weksteen4848ee02020-10-23 16:06:59 +0200294 RegisterCommand("api-prepare", [] { return std::unique_ptr<Command>(new PrepareCommand()); });
295 RegisterCommand("api-collect", [] { return std::unique_ptr<Command>(new CollectCommand()); });
Yabin Cui1befe4f2019-02-25 15:22:43 -0800296}
Yabin Cuiacbdb242020-07-07 15:56:34 -0700297
298} // namespace simpleperf