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> |
Yabin Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 20 | #include <regex> |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 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> |
Yabin Cui | 158a5fd | 2021-11-03 12:08:42 -0700 | [diff] [blame] | 28 | #include <android-base/properties.h> |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 29 | #include <android-base/strings.h> |
| 30 | #include <android-base/unique_fd.h> |
| 31 | #include <ziparchive/zip_writer.h> |
| 32 | |
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 | } |
| 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 | |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 147 | class CollectCommand : public Command { |
| 148 | public: |
| 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 |
Thiébaud Weksteen | 4848ee0 | 2020-10-23 16:06:59 +0200 | [diff] [blame] | 163 | ) { |
| 164 | } |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 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 | |
| 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 | |
| 191 | bool CollectCommand::ParseOptions(const std::vector<std::string>& args) { |
Yabin Cui | 6f09467 | 2020-07-22 14:50:35 -0700 | [diff] [blame] | 192 | OptionValueMap options; |
| 193 | std::vector<std::pair<OptionName, OptionValue>> ordered_options; |
Yabin Cui | 6f09467 | 2020-07-22 14:50:35 -0700 | [diff] [blame] | 194 | if (!PreprocessOptions(args, GetApiCollectCmdOptionFormats(), &options, &ordered_options, |
Yabin Cui | e09cb9f | 2020-11-03 09:24:48 -0800 | [diff] [blame] | 195 | nullptr)) { |
Yabin Cui | 6f09467 | 2020-07-22 14:50:35 -0700 | [diff] [blame] | 196 | return false; |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 197 | } |
Yabin Cui | 6f09467 | 2020-07-22 14:50:35 -0700 | [diff] [blame] | 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()); |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 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 | |
| 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 | |
| 235 | bool CollectCommand::CollectRecordingData() { |
Thiébaud Weksteen | 4848ee0 | 2020-10-23 16:06:59 +0200 | [diff] [blame] | 236 | std::unique_ptr<FILE, decltype(&fclose)> fp(android::base::Fdopen(std::move(out_fd_), "w"), |
| 237 | fclose); |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 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 | |
| 292 | bool CollectCommand::RemoveRecordingData() { |
| 293 | return Workload::RunCmd({"rm", "-rf", SIMPLEPERF_DATA_DIR}); |
| 294 | } |
| 295 | } // namespace |
| 296 | |
| 297 | void RegisterAPICommands() { |
Thiébaud Weksteen | 4848ee0 | 2020-10-23 16:06:59 +0200 | [diff] [blame] | 298 | RegisterCommand("api-prepare", [] { return std::unique_ptr<Command>(new PrepareCommand()); }); |
| 299 | RegisterCommand("api-collect", [] { return std::unique_ptr<Command>(new CollectCommand()); }); |
Yabin Cui | 1befe4f | 2019-02-25 15:22:43 -0800 | [diff] [blame] | 300 | } |
Yabin Cui | acbdb24 | 2020-07-07 15:56:34 -0700 | [diff] [blame] | 301 | |
| 302 | } // namespace simpleperf |