Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 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 <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 Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 27 | #include <android-base/properties.h> |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 28 | #include <android-base/strings.h> |
| 29 | #include <android-base/unique_fd.h> |
| 30 | #include <ziparchive/zip_writer.h> |
| 31 | |
Yabin Cui | f00f4fc | 2022-11-23 15:15:30 -0800 | [diff] [blame] | 32 | #include "RegEx.h" |
Yabin Cui | 6f09467 | 2020-07-22 14:50:35 -0700 | [diff] [blame] | 33 | #include "cmd_api_impl.h" |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 34 | #include "command.h" |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 35 | #include "environment.h" |
Thiébaud Weksteen | 4848ee0 | 2020-10-23 16:06:59 +0200 | [diff] [blame] | 36 | #include "event_type.h" |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 37 | #include "utils.h" |
| 38 | #include "workload.h" |
| 39 | |
Yabin Cui | faa7b92 | 2021-01-11 17:35:57 -0800 | [diff] [blame] | 40 | namespace simpleperf { |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 41 | namespace { |
Yabin Cui | faa7b92 | 2021-01-11 17:35:57 -0800 | [diff] [blame] | 42 | |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 43 | const std::string SIMPLEPERF_DATA_DIR = "simpleperf_data"; |
| 44 | |
| 45 | class PrepareCommand : public Command { |
| 46 | public: |
| 47 | PrepareCommand() |
Yabin Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 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 | ) {} |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 57 | bool Run(const std::vector<std::string>& args); |
Yabin Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 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; |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 65 | }; |
| 66 | |
Yabin Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 67 | bool PrepareCommand::Run(const std::vector<std::string>& args) { |
| 68 | if (!ParseOptions(args)) { |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 69 | return false; |
| 70 | } |
Yabin Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 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 | |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 98 | // Create tracepoint_events file. |
Yabin Cui | 16a6ace | 2020-10-01 14:56:32 -0700 | [diff] [blame] | 99 | return EventTypeManager::Instance().WriteTracepointsToFile("/data/local/tmp/tracepoint_events"); |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 100 | } |
| 101 | |
Yabin Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 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 | |
| 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 | } |
Yabin Cui | f00f4fc | 2022-11-23 15:15:30 -0800 | [diff] [blame] | 129 | 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 Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 133 | uint32_t uid; |
Yabin Cui | f00f4fc | 2022-11-23 15:15:30 -0800 | [diff] [blame] | 134 | if (name == app_name_ && android::base::ParseUint(match->GetField(2), &uid)) { |
Yabin Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 135 | return uid; |
| 136 | } |
Yabin Cui | f00f4fc | 2022-11-23 15:15:30 -0800 | [diff] [blame] | 137 | match->MoveToNextMatch(); |
Yabin Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 138 | } |
| 139 | LOG(ERROR) << "failed to find package " << app_name_; |
| 140 | return std::nullopt; |
| 141 | } |
| 142 | |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 143 | class 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 Weksteen | 4848ee0 | 2020-10-23 16:06:59 +0200 | [diff] [blame] | 159 | ) { |
| 160 | } |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 161 | 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 | |
| 176 | bool 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 | |
| 187 | bool CollectCommand::ParseOptions(const std::vector<std::string>& args) { |
Yabin Cui | 6f09467 | 2020-07-22 14:50:35 -0700 | [diff] [blame] | 188 | OptionValueMap options; |
| 189 | std::vector<std::pair<OptionName, OptionValue>> ordered_options; |
Yabin Cui | 6f09467 | 2020-07-22 14:50:35 -0700 | [diff] [blame] | 190 | if (!PreprocessOptions(args, GetApiCollectCmdOptionFormats(), &options, &ordered_options, |
Yabin Cui | e09cb9f | 2020-11-03 09:24:48 -0800 | [diff] [blame] | 191 | nullptr)) { |
Yabin Cui | 6f09467 | 2020-07-22 14:50:35 -0700 | [diff] [blame] | 192 | return false; |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 193 | } |
Yabin Cui | 6f09467 | 2020-07-22 14:50:35 -0700 | [diff] [blame] | 194 | |
| 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 Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 212 | 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 | |
| 221 | void 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 | |
| 231 | bool CollectCommand::CollectRecordingData() { |
Thiébaud Weksteen | 4848ee0 | 2020-10-23 16:06:59 +0200 | [diff] [blame] | 232 | std::unique_ptr<FILE, decltype(&fclose)> fp(android::base::Fdopen(std::move(out_fd_), "w"), |
| 233 | fclose); |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 234 | 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 | |
| 288 | bool CollectCommand::RemoveRecordingData() { |
| 289 | return Workload::RunCmd({"rm", "-rf", SIMPLEPERF_DATA_DIR}); |
| 290 | } |
| 291 | } // namespace |
| 292 | |
| 293 | void RegisterAPICommands() { |
Thiébaud Weksteen | 4848ee0 | 2020-10-23 16:06:59 +0200 | [diff] [blame] | 294 | RegisterCommand("api-prepare", [] { return std::unique_ptr<Command>(new PrepareCommand()); }); |
| 295 | RegisterCommand("api-collect", [] { return std::unique_ptr<Command>(new CollectCommand()); }); |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 296 | } |
Yabin Cui | acbdb24 | 2020-07-07 15:56:34 -0700 | [diff] [blame] | 297 | |
| 298 | } // namespace simpleperf |