/* * 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 "command.h" #include "event_type.h" #include "environment.h" #include "utils.h" #include "workload.h" namespace { const std::string SIMPLEPERF_DATA_DIR = "simpleperf_data"; class PrepareCommand : public Command { public: PrepareCommand() : Command("api-prepare", "Prepare recording via app api", "Usage: simpleperf api-prepare\n" ) {} bool Run(const std::vector& args); }; bool PrepareCommand::Run(const std::vector&) { // Enable profiling. if (!CheckPerfEventLimit()) { return false; } // Create tracepoint_events file. if (!android::base::WriteStringToFile(GetTracepointEvents(), "/data/local/tmp/tracepoint_events")) { PLOG(ERROR) << "failed to write tracepoint_events file"; return false; } return true; } 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) { for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "--app") { if (!NextArgumentOrError(args, &i)) { return false; } app_name_ = args[i]; } else if (args[i] == "--in-app") { in_app_context_ = true; } else if (args[i] == "-o") { if (!NextArgumentOrError(args, &i)) { return false; } output_filepath_ = args[i]; } else if (args[i] == "--out-fd") { int fd; if (!GetUintOption(args, &i, &fd)) { return false; } out_fd_.reset(fd); } else if (args[i] == "--stop-signal-fd") { int fd; if (!GetUintOption(args, &i, &fd)) { return false; } stop_signal_fd_.reset(fd); } else { ReportUnknownOption(args, i); return false; } } 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()); }); }