| /* |
| * 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 <stdio.h> |
| |
| #include <memory> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/parseint.h> |
| #include <android-base/properties.h> |
| #include <android-base/strings.h> |
| #include <android-base/unique_fd.h> |
| #include <ziparchive/zip_writer.h> |
| |
| #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 <package_name> the android application to record via app api\n" |
| "--days <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<std::string>& args); |
| |
| private: |
| bool ParseOptions(const std::vector<std::string>& args); |
| std::optional<uint32_t> GetAppUid(); |
| |
| std::string app_name_; |
| uint64_t days_ = 0; |
| }; |
| |
| bool PrepareCommand::Run(const std::vector<std::string>& 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<uint32_t> 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<std::string>& args) { |
| OptionValueMap options; |
| std::vector<std::pair<OptionName, OptionValue>> 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<uint32_t> PrepareCommand::GetAppUid() { |
| std::unique_ptr<FILE, decltype(&pclose)> 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 <package_name> 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 <fd> Write output to a file descriptor.\n" |
| "--stop-signal-fd <fd> Stop recording when fd is readable.\n" |
| #endif |
| // clang-format on |
| ) { |
| } |
| bool Run(const std::vector<std::string>& args); |
| |
| private: |
| bool ParseOptions(const std::vector<std::string>& 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<std::string>& 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<std::string>& args) { |
| OptionValueMap options; |
| std::vector<std::pair<OptionName, OptionValue>> 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<int>(value->uint_value)); |
| } |
| if (auto value = options.PullValue("--stop-signal-fd"); value) { |
| stop_signal_fd_.reset(static_cast<int>(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<void>(read(fd, &c, 1)); |
| exit(1); |
| }); |
| thread.detach(); |
| } |
| |
| bool CollectCommand::CollectRecordingData() { |
| std::unique_ptr<FILE, decltype(&fclose)> fp(android::base::Fdopen(std::move(out_fd_), "w"), |
| fclose); |
| if (fp == nullptr) { |
| PLOG(ERROR) << "failed to call fdopen"; |
| return false; |
| } |
| std::vector<char> 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<Command>(new PrepareCommand()); }); |
| RegisterCommand("api-collect", [] { return std::unique_ptr<Command>(new CollectCommand()); }); |
| } |
| |
| } // namespace simpleperf |