diff options
-rw-r--r-- | simpleperf/Android.bp | 1 | ||||
-rw-r--r-- | simpleperf/app_api/cpp/simpleperf.cpp | 66 | ||||
-rw-r--r-- | simpleperf/app_api/cpp/simpleperf.h | 3 | ||||
-rw-r--r-- | simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java | 59 | ||||
-rw-r--r-- | simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java | 21 | ||||
-rw-r--r-- | simpleperf/cmd_api.cpp | 223 | ||||
-rw-r--r-- | simpleperf/command.cpp | 4 | ||||
-rw-r--r-- | simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp | 1 | ||||
-rw-r--r-- | simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java | 2 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 6 | ||||
-rw-r--r-- | simpleperf/environment.h | 1 | ||||
-rwxr-xr-x | simpleperf/scripts/api_app_profiler.py | 153 | ||||
-rwxr-xr-x | simpleperf/scripts/api_profiler.py | 117 | ||||
-rw-r--r-- | simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp | 5 |
14 files changed, 474 insertions, 188 deletions
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp index 096f7bf0..d45cc6a8 100644 --- a/simpleperf/Android.bp +++ b/simpleperf/Android.bp @@ -265,6 +265,7 @@ cc_defaults { linux: { srcs: [ "CallChainJoiner.cpp", + "cmd_api.cpp", "cmd_debug_unwind.cpp", "cmd_list.cpp", "cmd_record.cpp", diff --git a/simpleperf/app_api/cpp/simpleperf.cpp b/simpleperf/app_api/cpp/simpleperf.cpp index 11e70e6f..23bb4fb5 100644 --- a/simpleperf/app_api/cpp/simpleperf.cpp +++ b/simpleperf/app_api/cpp/simpleperf.cpp @@ -23,6 +23,7 @@ #include <sys/socket.h> #include <sys/stat.h> #include <sys/wait.h> +#include <time.h> #include <unistd.h> #include <mutex> @@ -38,7 +39,7 @@ enum RecordCmd { class RecordOptionsImpl { public: - std::string output_filename = "perf.data"; + std::string output_filename; std::string event = "cpu-cycles"; size_t freq = 4000; double duration_in_second = 0.0; @@ -97,9 +98,27 @@ RecordOptions& RecordOptions::TraceOffCpu() { return *this; } +static std::string GetDefaultOutputFilename() { + time_t t = time(nullptr); + struct tm tm; + if (localtime_r(&t, &tm) != &tm) { + return "perf.data"; + } + char* buf = nullptr; + asprintf(&buf, "perf-%02d-%02d-%02d-%02d-%02d.data", tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec); + std::string result = buf; + free(buf); + return result; +} + std::vector<std::string> RecordOptions::ToRecordArgs() const { std::vector<std::string> args; - args.insert(args.end(), {"-o", impl_->output_filename}); + std::string output_filename = impl_->output_filename; + if (output_filename.empty()) { + output_filename = GetDefaultOutputFilename(); + } + args.insert(args.end(), {"-o", output_filename}); args.insert(args.end(), {"-e", impl_->event}); args.insert(args.end(), {"-f", std::to_string(impl_->freq)}); if (impl_->duration_in_second != 0.0) { @@ -147,6 +166,7 @@ class ProfileSessionImpl { private: std::string FindSimpleperf(); + std::string FindSimpleperfInTempDir(); void CheckIfPerfEnabled(); void CreateSimpleperfDataDir(); void CreateSimpleperfProcess(const std::string& simpleperf_path, @@ -251,21 +271,39 @@ static bool IsExecutableFile(const std::string& path) { } std::string ProfileSessionImpl::FindSimpleperf() { - std::vector<std::string> candidates = { - // For debuggable apps, simpleperf is put to the appDir by api_app_profiler.py. - app_data_dir_ + "/simpleperf", - // For profileable apps on Android >= Q, use simpleperf in system image. - "/system/bin/simpleperf" - }; - for (const auto& path : candidates) { - if (IsExecutableFile(path)) { - return path; - } + // 1. Try /data/local/tmp/simpleperf first. Probably it's newer than /system/bin/simpleperf. + std::string simpleperf_path = FindSimpleperfInTempDir(); + if (!simpleperf_path.empty()) { + return simpleperf_path; } - Abort("can't find simpleperf on device. Please run api_app_profiler.py."); + // 2. Try /system/bin/simpleperf, which is available on Android >= Q. + simpleperf_path = "/system/bin/simpleperf"; + if (IsExecutableFile(simpleperf_path)) { + return simpleperf_path; + } + Abort("can't find simpleperf on device. Please run api_profiler.py."); return ""; } +std::string ProfileSessionImpl::FindSimpleperfInTempDir() { + const std::string path = "/data/local/tmp/simpleperf"; + if (!IsExecutableFile(path)) { + return ""; + } + // Copy it to app_dir to execute it. + const std::string to_path = app_data_dir_ + "/simpleperf"; + const std::string copy_cmd = "cp " + path + " " + to_path; + if (system(copy_cmd.c_str()) != 0) { + return ""; + } + const std::string test_cmd = to_path; + // For apps with target sdk >= 29, executing app data file isn't allowed. So test executing it. + if (system(test_cmd.c_str()) != 0) { + return ""; + } + return to_path; +} + static std::string ReadFile(FILE* fp) { std::string s; char buf[200]; @@ -287,7 +325,7 @@ void ProfileSessionImpl::CheckIfPerfEnabled() { std::string s = ReadFile(fp); pclose(fp); if (!s.empty() && s[0] == '1') { - Abort("linux perf events aren't enabled on the device. Please run api_app_profiler.py."); + Abort("linux perf events aren't enabled on the device. Please run api_profiler.py."); } } diff --git a/simpleperf/app_api/cpp/simpleperf.h b/simpleperf/app_api/cpp/simpleperf.h index d6704fc8..309b37b7 100644 --- a/simpleperf/app_api/cpp/simpleperf.h +++ b/simpleperf/app_api/cpp/simpleperf.h @@ -42,7 +42,8 @@ class RecordOptions { RecordOptions(); ~RecordOptions(); /** - * Set output filename. Default is perf.data. The file will be generated under simpleperf_data/. + * Set output filename. Default is perf-<month>-<day>-<hour>-<minute>-<second>.data. + * The file will be generated under simpleperf_data/. */ RecordOptions& SetOutputFilename(const std::string& filename); diff --git a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java index 10cbb518..f1e2b202 100644 --- a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java +++ b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java @@ -174,19 +174,52 @@ public class ProfileSession { } private String findSimpleperf() { - String[] candidates = new String[]{ - // For debuggable apps, simpleperf is put to the appDir by api_app_profiler.py. - appDataDir + "/simpleperf", - // For profileable apps on Android >= Q, use simpleperf in system image. - "/system/bin/simpleperf" - }; - for (String path : candidates) { - File file = new File(path); - if (file.isFile()) { - return path; - } + // 1. Try /data/local/tmp/simpleperf. Probably it's newer than /system/bin/simpleperf. + String simpleperfPath = findSimpleperfInTempDir(); + if (simpleperfPath != null) { + return simpleperfPath; + } + // 2. Try /system/bin/simpleperf, which is available on Android >= Q. + simpleperfPath = "/system/bin/simpleperf"; + if (isExecutableFile(simpleperfPath)) { + return simpleperfPath; + } + throw new Error("can't find simpleperf on device. Please run api_profiler.py."); + } + + private boolean isExecutableFile(String path) { + File file = new File(path); + return file.canExecute(); + } + + private String findSimpleperfInTempDir() { + String path = "/data/local/tmp/simpleperf"; + File file = new File(path); + if (!file.isFile()){ + return null; + } + // Copy it to app dir to execute it. + String toPath = appDataDir + "/simpleperf"; + try { + Process process = new ProcessBuilder() + .command("cp", path, toPath).start(); + process.waitFor(); + } catch (Exception e) { + return null; + } + if (!isExecutableFile(toPath)) { + return null; + } + // For apps with target sdk >= 29, executing app data file isn't allowed. So test executing + // it. + try { + Process process = new ProcessBuilder() + .command(toPath).start(); + process.waitFor(); + } catch (Exception e) { + return null; } - throw new Error("can't find simpleperf on device. Please run api_app_profiler.py."); + return toPath; } private void checkIfPerfEnabled() { @@ -206,7 +239,7 @@ public class ProfileSession { value = readInputStream(process.getInputStream()); if (value.startsWith("1")) { throw new Error("linux perf events aren't enabled on the device." + - " Please run api_app_profiler.py."); + " Please run api_profiler.py."); } } diff --git a/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java b/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java index de5a715c..3ed39fb7 100644 --- a/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java +++ b/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java @@ -17,6 +17,9 @@ package com.android.simpleperf; import android.system.Os; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; @@ -39,7 +42,8 @@ import java.util.List; public class RecordOptions { /** - * Set output filename. Default is perf.data. The file will be generated under simpleperf_data/. + * Set output filename. Default is perf-<month>-<day>-<hour>-<minute>-<second>.data. + * The file will be generated under simpleperf_data/. */ public RecordOptions setOutputFilename(String filename) { outputFilename = filename; @@ -110,8 +114,13 @@ public class RecordOptions { */ public List<String> toRecordArgs() { ArrayList<String> args = new ArrayList<>(); + + String filename = outputFilename; + if (filename == null) { + filename = getDefaultOutputFilename(); + } args.add("-o"); - args.add(outputFilename); + args.add(filename); args.add("-e"); args.add(event); args.add("-f"); @@ -146,7 +155,13 @@ public class RecordOptions { return args; } - private String outputFilename = "perf.data"; + private String getDefaultOutputFilename() { + LocalDateTime time = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("'perf'-MM-dd-HH-mm-ss'.data'"); + return time.format(formatter); + } + + private String outputFilename; private String event = "cpu-cycles"; private int freq = 4000; private double durationInSecond = 0.0; diff --git a/simpleperf/cmd_api.cpp b/simpleperf/cmd_api.cpp new file mode 100644 index 00000000..7f933736 --- /dev/null +++ b/simpleperf/cmd_api.cpp @@ -0,0 +1,223 @@ +/* + * 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/strings.h> +#include <android-base/unique_fd.h> +#include <ziparchive/zip_writer.h> + +#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<std::string>& args); +}; + +bool PrepareCommand::Run(const std::vector<std::string>&) { + // 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 <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) { + 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<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()); }); +} diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp index bfb6b16f..4c83540c 100644 --- a/simpleperf/command.cpp +++ b/simpleperf/command.cpp @@ -96,6 +96,7 @@ extern void RegisterReportSampleCommand(); extern void RegisterStatCommand(); extern void RegisterDebugUnwindCommand(); extern void RegisterTraceSchedCommand(); +extern void RegisterAPICommands(); class CommandRegister { public: @@ -111,6 +112,9 @@ class CommandRegister { RegisterStatCommand(); RegisterDebugUnwindCommand(); RegisterTraceSchedCommand(); +#if defined(__ANDROID__) + RegisterAPICommands(); +#endif #endif } }; diff --git a/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp b/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp index 73fac03e..a47e73b6 100644 --- a/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp +++ b/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp @@ -47,6 +47,7 @@ void* ProfileThreadFunc(void*) { sleep(1); log("stop recording"); session.StopRecording(); + log("stop recording successfully"); profile_thread_exited = true; return nullptr; }; diff --git a/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java b/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java index bf3b0c2e..2cb96f03 100644 --- a/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java +++ b/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java @@ -60,7 +60,7 @@ public class MainActivity extends AppCompatActivity { Thread.sleep(1000); Log.e("simpleperf", "stopRecording"); profileSession.stopRecording(); - Log.e("simpleperf", "stopRecording success"); + Log.e("simpleperf", "stopRecording successfully"); } catch (Exception e) { Log.e("simpleperf", "exception: " + e.getMessage()); } diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp index e82162b5..3591626e 100644 --- a/simpleperf/environment.cpp +++ b/simpleperf/environment.cpp @@ -623,6 +623,10 @@ std::set<pid_t> WaitForAppProcesses(const std::string& package_name) { } } +bool IsAppDebuggable(const std::string& package_name) { + return Workload::RunCmd({"run-as", package_name, "echo", ">/dev/null", "2>/dev/null"}, false); +} + namespace { class InAppRunner { @@ -767,7 +771,7 @@ class RunAs : public InAppRunner { bool RunAs::Prepare() { // Test if run-as can access the package. - if (!Workload::RunCmd({"run-as", package_name_, "echo", ">/dev/null", "2>/dev/null"}, false)) { + if (!IsAppDebuggable(package_name_)) { return false; } // run-as can't run /data/local/tmp/simpleperf directly. So copy simpleperf binary if needed. diff --git a/simpleperf/environment.h b/simpleperf/environment.h index e72dcecf..173cdcc6 100644 --- a/simpleperf/environment.h +++ b/simpleperf/environment.h @@ -103,6 +103,7 @@ ArchType GetMachineArch(); void PrepareVdsoFile(); std::set<pid_t> WaitForAppProcesses(const std::string& package_name); +bool IsAppDebuggable(const std::string& package_name); bool RunInAppContext(const std::string& app_package_name, const std::string& cmd, const std::vector<std::string>& args, size_t workload_args_size, const std::string& output_filepath, bool need_tracepoint_events); diff --git a/simpleperf/scripts/api_app_profiler.py b/simpleperf/scripts/api_app_profiler.py deleted file mode 100755 index 2c29a807..00000000 --- a/simpleperf/scripts/api_app_profiler.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python -# -# 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. -# - -""" - This script is part of controlling simpleperf recording in user code. It is used to prepare - profiling environment (upload simpleperf to device and enable profiling) before recording - and collect profiling data on host after recording. - Controlling simpleperf recording is done in below steps: - 1. Add simpleperf Java API/C++ API to the app's source code. And call the API in user code. - 2. Run `api_app_profiler.py prepare` to prepare profiling environment. - 3. Run the app one or more times to generate profiling data. - 4. Run `api_app_profiler.py collect` to collect profiling data on host. -""" - -from __future__ import print_function -import argparse -import os -import os.path -import shutil - -from utils import AdbHelper, get_target_binary_path, log_exit, remove - -def prepare_recording(args): - adb = AdbHelper() - enable_profiling_on_device(adb, args) - # TODO: support profileable app in Android >= Q. - if not is_debuggable_app(adb, args.app[0]): - log_exit("The app isn't debuggable: %s" % args.app[0]) - upload_simpleperf_to_device(adb, args) - prepare_tracepoint_events_file(adb) - -def is_debuggable_app(adb, app): - result, _ = adb.run_and_return_output(["shell", "run-as", app, "echo"], log_output=False) - return result - -def enable_profiling_on_device(adb, args): - android_version = adb.get_android_version() - if android_version >= 10: - adb.set_property('debug.perf_event_max_sample_rate', str(args.max_sample_rate[0])) - adb.set_property('debug.perf_cpu_time_max_percent', str(args.max_cpu_percent[0])) - adb.set_property('debug.perf_event_mlock_kb', str(args.max_memory_in_kb[0])) - adb.set_property('security.perf_harden', '0') - -def upload_simpleperf_to_device(adb, args): - device_arch = adb.get_device_arch() - simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf') - adb.check_run(['push', simpleperf_binary, '/data/local/tmp']) - adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) - adb.check_run(['shell', 'run-as', args.app[0], 'cp', '/data/local/tmp/simpleperf', '.']) - -def prepare_tracepoint_events_file(adb): - tracepoint_event_ids = adb.check_run_and_return_output( - ['shell', 'ls', '/sys/kernel/debug/tracing/events/*/*/id'], log_output=False) - events_file_path = 'tracepoint_events' - with open(events_file_path, 'w') as fh: - for i, id_path in enumerate(tracepoint_event_ids.split()): - # For id_path like "/sys/kernel/debug/tracing/events/sched/sched_switch/id", - # get event_name "sched:sched_switch". - items = id_path.split('/') - event_name = items[-3] + ':' + items[-2] - id_value = adb.check_run_and_return_output(['shell', 'cat', id_path], log_output=False) - id_value = id_value.strip() - if i > 0: - fh.write('\n') - fh.write('%s %s' % (event_name, id_value)) - adb.check_run(['push', events_file_path, '/data/local/tmp/tracepoint_events']) - remove(events_file_path) - - -def collect_data(args): - adb = AdbHelper() - if not os.path.isdir(args.out_dir): - os.makedirs(args.out_dir) - move_profiling_data_to_tmp_dir(adb, args.app[0]) - adb.check_run(['pull', '/data/local/tmp/simpleperf_data', args.out_dir]) - adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/simpleperf_data']) - simpleperf_data_path = os.path.join(args.out_dir, 'simpleperf_data') - for name in os.listdir(simpleperf_data_path): - remove(os.path.join(args.out_dir, name)) - shutil.move(os.path.join(simpleperf_data_path, name), args.out_dir) - print('collect profiling data: %s' % os.path.join(args.out_dir, name)) - remove(simpleperf_data_path) - -def move_profiling_data_to_tmp_dir(adb, app): - """ move /data/data/<app>/simpleperf_data to /data/local/tmp/simpleperf_data.""" - # TODO: support profileable app in Android >= Q. - if not is_debuggable_app(adb, app): - log_exit("The app isn't debuggable: %s" % app) - result, output = adb.run_and_return_output(['shell', 'run-as', app, 'ls', 'simpleperf_data']) - if not result: - log_exit("can't find profiling data for app %s" % app) - file_list = output.split() - shell_script = 'collect_data_shell_script.sh' - with open(shell_script, 'w') as fh: - fh.write('set -ex\n') - fh.write('rm -rf /data/local/tmp/simpleperf_data\n') - fh.write('mkdir /data/local/tmp/simpleperf_data\n') - for filename in file_list: - fh.write('run-as %s cat simpleperf_data/%s >/data/local/tmp/simpleperf_data/%s\n' % ( - app, filename, filename)) - adb.check_run(['push', shell_script, '/data/local/tmp']) - adb.check_run(['shell', 'sh', '/data/local/tmp/%s' % shell_script]) - adb.check_run(['shell', 'run-as', app, 'rm', '-rf', 'simpleperf_data']) - adb.check_run(['shell', 'rm', '/data/local/tmp/%s' % shell_script]) - remove(shell_script) - - -class ArgumentHelpFormatter(argparse.ArgumentDefaultsHelpFormatter, - argparse.RawDescriptionHelpFormatter): - pass - -def main(): - parser = argparse.ArgumentParser(description=__doc__, - formatter_class=ArgumentHelpFormatter) - subparsers = parser.add_subparsers() - prepare_parser = subparsers.add_parser('prepare', help='Prepare recording on device.', - formatter_class=ArgumentHelpFormatter) - prepare_parser.add_argument('-p', '--app', nargs=1, required=True, help=""" - The package name of the app to profile.""") - prepare_parser.add_argument('--max-sample-rate', nargs=1, type=int, default=[100000], help=""" - Set max sample rate (only on Android >= Q).""") - prepare_parser.add_argument('--max-cpu-percent', nargs=1, type=int, default=[25], help=""" - Set max cpu percent for recording (only on Android >= Q).""") - prepare_parser.add_argument('--max-memory-in-kb', nargs=1, type=int, default=[516], help=""" - Set max kernel buffer size for recording (only on Android >= Q). - """) - prepare_parser.set_defaults(func=prepare_recording) - collect_parser = subparsers.add_parser('collect', help='Collect profiling data.', - formatter_class=ArgumentHelpFormatter) - collect_parser.add_argument('-p', '--app', nargs=1, required=True, help=""" - The app package name of the app profiled.""") - collect_parser.add_argument('-o', '--out-dir', default='simpleperf_data', help=""" - The directory to store profiling data.""") - collect_parser.set_defaults(func=collect_data) - args = parser.parse_args() - args.func(args) - -if __name__ == '__main__': - main() diff --git a/simpleperf/scripts/api_profiler.py b/simpleperf/scripts/api_profiler.py new file mode 100755 index 00000000..424de698 --- /dev/null +++ b/simpleperf/scripts/api_profiler.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# +# 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. +# + +""" + This script is part of controlling simpleperf recording in user code. It is used to prepare + profiling environment (upload simpleperf to device and enable profiling) before recording + and collect recording data on host after recording. + Controlling simpleperf recording is done in below steps: + 1. Add simpleperf Java API/C++ API to the app's source code. And call the API in user code. + 2. Run `api_profiler.py prepare` to prepare profiling environment. + 3. Run the app one or more times to generate recording data. + 4. Run `api_profiler.py collect` to collect recording data on host. +""" + +from __future__ import print_function +import argparse +import os +import os.path +import shutil +import zipfile + +from utils import AdbHelper, get_target_binary_path, log_exit, log_info, remove + +def prepare_recording(args): + adb = AdbHelper() + enable_profiling_on_device(adb, args) + upload_simpleperf_to_device(adb) + run_simpleperf_prepare_cmd(adb) + +def enable_profiling_on_device(adb, args): + android_version = adb.get_android_version() + if android_version >= 10: + adb.set_property('debug.perf_event_max_sample_rate', str(args.max_sample_rate[0])) + adb.set_property('debug.perf_cpu_time_max_percent', str(args.max_cpu_percent[0])) + adb.set_property('debug.perf_event_mlock_kb', str(args.max_memory_in_kb[0])) + adb.set_property('security.perf_harden', '0') + +def upload_simpleperf_to_device(adb): + device_arch = adb.get_device_arch() + simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf') + adb.check_run(['push', simpleperf_binary, '/data/local/tmp']) + adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) + +def run_simpleperf_prepare_cmd(adb): + adb.check_run(['shell', '/data/local/tmp/simpleperf', 'api-prepare']) + + +def collect_data(args): + adb = AdbHelper() + if not os.path.isdir(args.out_dir): + os.makedirs(args.out_dir) + download_recording_data(adb, args) + unzip_recording_data(args) + +def download_recording_data(adb, args): + """ download recording data to simpleperf_data.zip.""" + upload_simpleperf_to_device(adb) + adb.check_run(['shell', '/data/local/tmp/simpleperf', 'api-collect', '--app', args.app[0], + '-o', '/data/local/tmp/simpleperf_data.zip']) + adb.check_run(['pull', '/data/local/tmp/simpleperf_data.zip', args.out_dir]) + adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/simpleperf_data']) + +def unzip_recording_data(args): + zip_file_path = os.path.join(args.out_dir, 'simpleperf_data.zip') + with zipfile.ZipFile(zip_file_path, 'r') as zip_fh: + names = zip_fh.namelist() + log_info('There are %d recording data files.' % len(names)) + for name in names: + log_info('recording file: %s' % os.path.join(args.out_dir, name)) + zip_fh.extract(name, args.out_dir) + remove(zip_file_path) + +class ArgumentHelpFormatter(argparse.ArgumentDefaultsHelpFormatter, + argparse.RawDescriptionHelpFormatter): + pass + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=ArgumentHelpFormatter) + subparsers = parser.add_subparsers() + prepare_parser = subparsers.add_parser('prepare', help='Prepare recording on device.', + formatter_class=ArgumentHelpFormatter) + prepare_parser.add_argument('--max-sample-rate', nargs=1, type=int, default=[100000], help=""" + Set max sample rate (only on Android >= Q).""") + prepare_parser.add_argument('--max-cpu-percent', nargs=1, type=int, default=[25], help=""" + Set max cpu percent for recording (only on Android >= Q).""") + prepare_parser.add_argument('--max-memory-in-kb', nargs=1, type=int, + default=[(1024 + 1) * 4 * 8], help=""" + Set max kernel buffer size for recording (only on Android >= Q). + """) + prepare_parser.set_defaults(func=prepare_recording) + collect_parser = subparsers.add_parser('collect', help='Collect recording data.', + formatter_class=ArgumentHelpFormatter) + collect_parser.add_argument('-p', '--app', nargs=1, required=True, help=""" + The app package name of the app profiled.""") + collect_parser.add_argument('-o', '--out-dir', default='simpleperf_data', help=""" + The directory to store recording data.""") + collect_parser.set_defaults(func=collect_data) + args = parser.parse_args() + args.func(args) + +if __name__ == '__main__': + main() diff --git a/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp b/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp index a5b1656e..aeddee9e 100644 --- a/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp +++ b/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp @@ -98,8 +98,9 @@ std::vector<gid_t> GetSupplementaryGids(uid_t userAppId) { } static void CheckSimpleperfArguments(const char* cmdname, char** args) { - if (strcmp(cmdname, "stat") != 0 && strcmp(cmdname, "record") != 0) { - error(1, 0, "only stat/record commands are allowed"); + if (strcmp(cmdname, "stat") != 0 && strcmp(cmdname, "record") != 0 && + strcmp(cmdname, "api-collect") != 0) { + error(1, 0, "cmd isn't allowed: %s", cmdname); } std::set<std::string> zero_arg_options = { "-b", "--csv", "--exit-with-parent", "-g", "--in-app", "--interval-only-values", |