/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "RegEx.h" #include "cmd_api_impl.h" #include "command.h" #include "environment.h" #include "event_type.h" #include "utils.h" #include "workload.h" namespace simpleperf { namespace { const std::string SIMPLEPERF_DATA_DIR = "simpleperf_data"; class PrepareCommand : public Command { public: PrepareCommand() : Command("api-prepare", "Prepare recording via app api", // clang-format off "Usage: simpleperf api-prepare [options]\n" "--app the android application to record via app api\n" "--days By default, the recording permission is reset after device reboot.\n" " But on Android >= 13, we can use this option to set how long we want\n" " the permission to last. It can last after device reboot.\n" // clang-format on ) {} bool Run(const std::vector& args); private: bool ParseOptions(const std::vector& args); std::optional GetAppUid(); std::string app_name_; uint64_t days_ = 0; }; bool PrepareCommand::Run(const std::vector& args) { if (!ParseOptions(args)) { return false; } // Enable profiling. if (GetAndroidVersion() >= 13 && !app_name_.empty() && days_ != 0) { // Enable app recording via persist properties. uint64_t duration_in_sec; uint64_t expiration_time; if (__builtin_mul_overflow(days_, 24 * 3600, &duration_in_sec) || __builtin_add_overflow(time(nullptr), duration_in_sec, &expiration_time)) { expiration_time = UINT64_MAX; } std::optional uid = GetAppUid(); if (!uid) { return false; } if (!android::base::SetProperty("persist.simpleperf.profile_app_uid", std::to_string(uid.value())) || !android::base::SetProperty("persist.simpleperf.profile_app_expiration_time", std::to_string(expiration_time))) { LOG(ERROR) << "failed to set system properties"; return false; } } else { // Enable app recording via security.perf_harden. if (!CheckPerfEventLimit()) { return false; } } // Create tracepoint_events file. return EventTypeManager::Instance().WriteTracepointsToFile("/data/local/tmp/tracepoint_events"); } bool PrepareCommand::ParseOptions(const std::vector& args) { OptionValueMap options; std::vector> ordered_options; static const OptionFormatMap option_formats = { {"--app", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}}, {"--days", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}}, }; if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) { return false; } if (auto value = options.PullValue("--app"); value) { app_name_ = *value->str_value; } if (!options.PullUintValue("--days", &days_)) { return false; } return true; } std::optional PrepareCommand::GetAppUid() { std::unique_ptr fp(popen("pm list packages -U", "re"), pclose); std::string content; if (!fp || !android::base::ReadFdToString(fileno(fp.get()), &content)) { PLOG(ERROR) << "failed to run `pm list packages -U`"; return std::nullopt; } auto re = RegEx::Create(R"(package:([\w\.]+)\s+uid:(\d+))"); auto match = re->SearchAll(content); while (match->IsValid()) { std::string name = match->GetField(1); uint32_t uid; if (name == app_name_ && android::base::ParseUint(match->GetField(2), &uid)) { return uid; } match->MoveToNextMatch(); } LOG(ERROR) << "failed to find package " << app_name_; return std::nullopt; } class CollectCommand : public Command { public: CollectCommand() : Command("api-collect", "Collect recording data generated by app api", // clang-format off "Usage: simpleperf api-collect [options]\n" "--app the android application having recording data\n" "-o record_zipfile_path the path to store recording data\n" " Default is simpleperf_data.zip.\n" #if 0 // Below options are only used internally and shouldn't be visible to the public. "--in-app We are already running in the app's context.\n" "--out-fd Write output to a file descriptor.\n" "--stop-signal-fd Stop recording when fd is readable.\n" #endif // clang-format on ) { } bool Run(const std::vector& args); private: bool ParseOptions(const std::vector& args); void HandleStopSignal(); bool CollectRecordingData(); bool RemoveRecordingData(); std::string app_name_; std::string output_filepath_ = "simpleperf_data.zip"; bool in_app_context_ = false; android::base::unique_fd out_fd_; android::base::unique_fd stop_signal_fd_; }; bool CollectCommand::Run(const std::vector& args) { if (!ParseOptions(args)) { return false; } if (in_app_context_) { HandleStopSignal(); return CollectRecordingData() && RemoveRecordingData(); } return RunInAppContext(app_name_, Name(), args, 0, output_filepath_, false); } bool CollectCommand::ParseOptions(const std::vector& args) { OptionValueMap options; std::vector> ordered_options; if (!PreprocessOptions(args, GetApiCollectCmdOptionFormats(), &options, &ordered_options, nullptr)) { return false; } if (auto value = options.PullValue("--app"); value) { app_name_ = *value->str_value; } in_app_context_ = options.PullBoolValue("--in-app"); if (auto value = options.PullValue("-o"); value) { output_filepath_ = *value->str_value; } if (auto value = options.PullValue("--out-fd"); value) { out_fd_.reset(static_cast(value->uint_value)); } if (auto value = options.PullValue("--stop-signal-fd"); value) { stop_signal_fd_.reset(static_cast(value->uint_value)); } CHECK(options.values.empty()); CHECK(ordered_options.empty()); if (!in_app_context_) { if (app_name_.empty()) { LOG(ERROR) << "--app is missing"; return false; } } return true; } void CollectCommand::HandleStopSignal() { int fd = stop_signal_fd_.release(); std::thread thread([fd]() { char c; static_cast(read(fd, &c, 1)); exit(1); }); thread.detach(); } bool CollectCommand::CollectRecordingData() { std::unique_ptr fp(android::base::Fdopen(std::move(out_fd_), "w"), fclose); if (fp == nullptr) { PLOG(ERROR) << "failed to call fdopen"; return false; } std::vector buffer(64 * 1024); ZipWriter zip_writer(fp.get()); for (const auto& name : GetEntriesInDir(SIMPLEPERF_DATA_DIR)) { // No need to collect temporary files. const std::string path = SIMPLEPERF_DATA_DIR + "/" + name; if (android::base::StartsWith(name, "TemporaryFile-") || !IsRegularFile(path)) { continue; } int result = zip_writer.StartEntry(name.c_str(), ZipWriter::kCompress); if (result != 0) { LOG(ERROR) << "failed to start zip entry " << name << ": " << zip_writer.ErrorCodeString(result); return false; } android::base::unique_fd in_fd(FileHelper::OpenReadOnly(path)); if (in_fd == -1) { PLOG(ERROR) << "failed to open " << path; return false; } while (true) { ssize_t nread = TEMP_FAILURE_RETRY(read(in_fd, buffer.data(), buffer.size())); if (nread < 0) { PLOG(ERROR) << "failed to read " << path; return false; } if (nread == 0) { break; } result = zip_writer.WriteBytes(buffer.data(), nread); if (result != 0) { LOG(ERROR) << "failed to write zip entry " << name << ": " << zip_writer.ErrorCodeString(result); return false; } } result = zip_writer.FinishEntry(); if (result != 0) { LOG(ERROR) << "failed to finish zip entry " << name << ": " << zip_writer.ErrorCodeString(result); return false; } } int result = zip_writer.Finish(); if (result != 0) { LOG(ERROR) << "failed to finish zip writer: " << zip_writer.ErrorCodeString(result); return false; } return true; } bool CollectCommand::RemoveRecordingData() { return Workload::RunCmd({"rm", "-rf", SIMPLEPERF_DATA_DIR}); } } // namespace void RegisterAPICommands() { RegisterCommand("api-prepare", [] { return std::unique_ptr(new PrepareCommand()); }); RegisterCommand("api-collect", [] { return std::unique_ptr(new CollectCommand()); }); } } // namespace simpleperf