diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2017-07-18 07:34:05 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2017-07-18 07:34:05 +0000 |
commit | e544fa76b6c71c44c992bdafdfdd4e56b464bc91 (patch) | |
tree | 0f547312a5f4114b4de9d5a14f6cd00e6066cc0d | |
parent | 76c282846a3a29d55ca94c739ae726570ded0917 (diff) | |
parent | 5f91fea2e7870d7ea6fa404591c2575f541a6d1a (diff) | |
download | extras-e544fa76b6c71c44c992bdafdfdd4e56b464bc91.tar.gz |
release-request-e04bb055-13fc-41a1-8a9f-7fb10894ec3d-for-git_oc-mr1-release-4189380 snap-temp-L90600000083186678
Change-Id: I721f14bea25081e7a1a952d7288ec78796f89c44
-rw-r--r-- | simpleperf/README.md | 22 | ||||
-rw-r--r-- | simpleperf/cmd_record.cpp | 41 | ||||
-rw-r--r-- | simpleperf/cmd_record_test.cpp | 31 | ||||
-rw-r--r-- | simpleperf/cmd_stat.cpp | 10 | ||||
-rw-r--r-- | simpleperf/demo/README.md | 25 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 45 | ||||
-rw-r--r-- | simpleperf/environment.h | 3 | ||||
-rw-r--r-- | simpleperf/event_attr.cpp | 5 | ||||
-rw-r--r-- | simpleperf/event_type.cpp | 53 | ||||
-rw-r--r-- | simpleperf/event_type.h | 2 | ||||
-rw-r--r-- | simpleperf/inferno/inferno.py | 41 | ||||
-rw-r--r-- | simpleperf/nonlinux_support/nonlinux_support.cpp | 4 | ||||
-rw-r--r-- | simpleperf/scripts/annotate.config | 40 | ||||
-rw-r--r-- | simpleperf/scripts/annotate.py | 74 | ||||
-rw-r--r-- | simpleperf/scripts/app_profiler.config | 22 | ||||
-rw-r--r-- | simpleperf/scripts/app_profiler.py | 114 | ||||
-rw-r--r-- | simpleperf/scripts/binary_cache_builder.config | 27 | ||||
-rw-r--r-- | simpleperf/scripts/binary_cache_builder.py | 41 | ||||
-rw-r--r-- | simpleperf/scripts/pprof_proto_generator.config | 39 | ||||
-rw-r--r-- | simpleperf/scripts/pprof_proto_generator.py | 61 | ||||
-rw-r--r-- | simpleperf/scripts/utils.py | 90 | ||||
-rw-r--r-- | simpleperf/workload.cpp | 2 |
22 files changed, 512 insertions, 280 deletions
diff --git a/simpleperf/README.md b/simpleperf/README.md index 126fbc65..5311f03d 100644 --- a/simpleperf/README.md +++ b/simpleperf/README.md @@ -31,7 +31,6 @@ Bugs and feature requests can be submitted at http://github.com/android-ndk/ndk/ - [Answers to common issues](#answers-to-common-issues) - [The correct way to pull perf.data on host](#the-correct-way-to-pull-perfdata-on-host) - ## Simpleperf introduction ### Why simpleperf @@ -777,23 +776,16 @@ generate profiling data in a format acceptable by pprof. `annotate.py` reads perf.data, binaries in `binary-cache` (collected by `app-profiler.py`) and source code, and generates annoated source code in `annotated_files/`. -It is configured by `annotate.config`. - -**1. Fill `annotate.config`** - - Change `source_dirs` line to source_dirs = ["../SimpleperfExamplePureJava"] - Change `addr2line_path` line to addr2line_path = "addr2line" - -`addr2line` is need to annotate source code. It can be found in Android ndk release. -Please set `addr2line_path` to the location of `addr2line` if it can't be found -in PATH environment variable. - -**2. Run `annotate.py`** +**1. Run annotate.py** - $ python annotate.py + $ python annotate.py -s ../SimpleperfExamplePureJava +`addr2line` is need to annotate source code. It can be found in Android ndk +release, in paths like toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line. +Please use `--addr2line` option to set the path of `addr2line` if annotate.py +can't find it. -**3. Read annotated code** +**2. Read annotated code** The annotated source code is located at `annotated_files/`. `annotated_files/summary` shows how each source file is annotated. diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index d37c2a84..8bd7962f 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -153,6 +153,7 @@ class RecordCommand : public Command { #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" +"--tracepoint-events file_name Read tracepoint events from [file_name] instead of tracefs.\n" #endif // clang-format on ), @@ -241,14 +242,9 @@ class RecordCommand : public Command { }; bool RecordCommand::Run(const std::vector<std::string>& args) { - // 0. Do some environment preparation. if (!CheckPerfEventLimit()) { return false; } - if (!InitPerfClock()) { - return false; - } - PrepareVdsoFile(); // 1. Parse options, and use default measured event type if not given. std::vector<std::string> workload_args; @@ -261,7 +257,7 @@ bool RecordCommand::Run(const std::vector<std::string>& args) { // root. if (!IsRoot()) { return RunInAppContext(app_package_name_, "record", args, workload_args.size(), - record_filename_); + record_filename_, !event_selection_set_.GetTracepointEvents().empty()); } } if (event_selection_set_.empty()) { @@ -272,9 +268,15 @@ bool RecordCommand::Run(const std::vector<std::string>& args) { if (!SetEventSelectionFlags()) { return false; } + + // 2. Do some environment preparation. ScopedCurrentArch scoped_arch(GetMachineArch()); + if (!InitPerfClock()) { + return false; + } + PrepareVdsoFile(); - // 2. Create workload. + // 3. Create workload. std::unique_ptr<Workload> workload; if (!workload_args.empty()) { workload = Workload::CreateWorkload(workload_args); @@ -310,7 +312,7 @@ bool RecordCommand::Run(const std::vector<std::string>& args) { need_to_check_targets = true; } - // 3. Open perf_event_files, create mapped buffers for perf_event_files. + // 4. Open perf_event_files, create mapped buffers for perf_event_files. if (!event_selection_set_.OpenEventFiles(cpus_)) { return false; } @@ -319,12 +321,12 @@ bool RecordCommand::Run(const std::vector<std::string>& args) { return false; } - // 4. Create perf.data. + // 5. Create perf.data. if (!CreateAndInitRecordFile()) { return false; } - // 5. Add read/signal/periodic Events. + // 6. Add read/signal/periodic Events. auto callback = std::bind(&RecordCommand::ProcessRecord, this, std::placeholders::_1); if (!event_selection_set_.PrepareToReadMmapEventData(callback)) { @@ -348,7 +350,7 @@ bool RecordCommand::Run(const std::vector<std::string>& args) { } } - // 6. Write records in mapped buffers of perf_event_files to output file while + // 7. Write records in mapped buffers of perf_event_files to output file while // workload is running. start_sampling_time_in_ns_ = GetPerfClock(); LOG(VERBOSE) << "start_sampling_time is " << start_sampling_time_in_ns_ @@ -369,7 +371,7 @@ bool RecordCommand::Run(const std::vector<std::string>& args) { return false; } - // 7. Dump additional features, and close record file. + // 8. Dump additional features, and close record file. if (!DumpAdditionalFeatures(args)) { return false; } @@ -377,14 +379,14 @@ bool RecordCommand::Run(const std::vector<std::string>& args) { return false; } - // 8. Unwind dwarf callchain. + // 9. Unwind dwarf callchain. if (post_unwind_) { if (!PostUnwind(args)) { return false; } } - // 9. Show brief record result. + // 10. Show brief record result. LOG(INFO) << "Samples recorded: " << sample_record_count_ << ". Samples lost: " << lost_record_count_ << "."; if (sample_record_count_ + lost_record_count_ != 0) { @@ -584,6 +586,13 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args, return false; } event_selection_set_.AddMonitoredThreads(tids); + } else if (args[i] == "--tracepoint-events") { + if (!NextArgumentOrError(args, &i)) { + return false; + } + if (!SetTracepointEventsFilePath(args[i])) { + return false; + } } else { ReportUnknownOption(args, i); return false; @@ -726,8 +735,8 @@ bool RecordCommand::DumpKernelSymbol() { bool RecordCommand::DumpTracingData() { std::vector<const EventType*> tracepoint_event_types = event_selection_set_.GetTracepointEvents(); - if (tracepoint_event_types.empty()) { - return true; // No need to dump tracing data. + if (tracepoint_event_types.empty() || !CanRecordRawData()) { + return true; // No need to dump tracing data, or can't do it. } std::vector<char> tracing_data; if (!GetTracingData(tracepoint_event_types, &tracing_data)) { diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp index fcedcee1..8b70fcd2 100644 --- a/simpleperf/cmd_record_test.cpp +++ b/simpleperf/cmd_record_test.cpp @@ -429,3 +429,34 @@ TEST(record_cmd, cpu_clock_for_a_long_time) { ASSERT_TRUE(RecordCmd()->Run( {"-e", "cpu-clock", "-o", tmpfile.path, "-p", pid, "--duration", "3"})); } + +TEST(record_cmd, dump_regs_for_tracepoint_events) { + // Check if the kernel can dump registers for tracepoint events. + // If not, probably a kernel patch below is missing: + // "5b09a094f2 arm64: perf: Fix callchain parse error with kernel tracepoint events" + std::vector<std::unique_ptr<Workload>> workloads; + CreateProcesses(1, &workloads); + std::string pid = std::to_string(workloads[0]->GetPid()); + TemporaryFile tmpfile; + ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "-p", pid, "-e", "sched:sched_switch", + "-g", "--no-unwind", "--duration", "1"})); + + // If the kernel patch is missing, all regs dumped in sample records are zero. + std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path); + CHECK(reader != nullptr); + std::unique_ptr<Record> r; + bool regs_all_zero = true; + while (reader->ReadRecord(r) && r && regs_all_zero) { + if (r->type() != PERF_RECORD_SAMPLE) { + continue; + } + SampleRecord* s = static_cast<SampleRecord*>(r.get()); + for (size_t i = 0; i < s->regs_user_data.reg_nr; ++i) { + if (s->regs_user_data.regs[i] != 0u) { + regs_all_zero = false; + break; + } + } + } + ASSERT_FALSE(regs_all_zero); +} diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp index 4c79801c..a9d5036b 100644 --- a/simpleperf/cmd_stat.cpp +++ b/simpleperf/cmd_stat.cpp @@ -307,6 +307,7 @@ class StatCommand : public Command { #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" +"--tracepoint-events file_name Read tracepoint events from [file_name] instead of tracefs.\n" #endif // clang-format on ), @@ -359,7 +360,7 @@ bool StatCommand::Run(const std::vector<std::string>& args) { if (!app_package_name_.empty() && !in_app_context_) { if (!IsRoot()) { return RunInAppContext(app_package_name_, "stat", args, workload_args.size(), - output_filename_); + output_filename_, !event_selection_set_.GetTracepointEvents().empty()); } } if (event_selection_set_.empty()) { @@ -554,6 +555,13 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args, return false; } event_selection_set_.AddMonitoredThreads(tids); + } else if (args[i] == "--tracepoint-events") { + if (!NextArgumentOrError(args, &i)) { + return false; + } + if (!SetTracepointEventsFilePath(args[i])) { + return false; + } } else if (args[i] == "--verbose") { verbose_mode_ = true; } else { diff --git a/simpleperf/demo/README.md b/simpleperf/demo/README.md index 1ecd9dd5..2d2c377a 100644 --- a/simpleperf/demo/README.md +++ b/simpleperf/demo/README.md @@ -46,10 +46,7 @@ $ adb install -r app/build/outputs/apk/app-profiling.apk 2. Record profiling data: ``` $ cd ../../scripts/ -$ gvim app_profiler.config - change app_package_name line to: app_package_name = "com.example.simpleperf.simpleperfexamplepurejava" -$ python app_profiler.py - It runs the application and collects profiling data in perf.data, binaries on device in binary_cache/. +$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava ``` 3. Show profiling data: @@ -61,9 +58,7 @@ a. show call graph in txt mode b. show call graph in gui mode $ python report.py -g c. show samples in source code - $ gvim annotate.config - change source_dirs line to: source_dirs = ["../demo/SimpleperfExamplePureJava"] - $ python annotate.py + $ python annotate.py -s ../demo/SimpleperfExamplePureJava $ gvim annotated_files/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java check the annoated source file MainActivity.java. ``` @@ -89,9 +84,7 @@ $ adb install -r app/build/outputs/apk/app-profiling.apk 2. Record profiling data: ``` $ cd ../../scripts/ -$ gvim app_profiler.config - change app_package_name line to: app_package_name = "com.example.simpleperf.simpleperfexamplewithnative" -$ python app_profiler.py +$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative It runs the application and collects profiling data in perf.data, binaries on device in binary_cache/. ``` @@ -104,9 +97,7 @@ a. show call graph in txt mode b. show call graph in gui mode $ python report.py -g c. show samples in source code - $ gvim annotate.config - change source_dirs line to: source_dirs = ["../demo/SimpleperfExampleWithNative"] - $ python annotate.py + $ python annotate.py -s ../demo/SimpleperfExampleWithNative $ find . -name "native-lib.cpp" | xargs gvim check the annoated source file native-lib.cpp. ``` @@ -132,9 +123,7 @@ $ adb install -r app/build/outputs/apk/profiling/app-profiling.apk 2. Record profiling data: ``` $ cd ../../scripts/ -$ gvim app_profiler.config - change app_package_name line to: app_package_name = "com.example.simpleperf.simpleperfexampleofkotlin" -$ python app_profiler.py +$ python app_profiler.py -p com.example.simpleperf.simpleperfexampleofkotlin It runs the application and collects profiling data in perf.data, binaries on device in binary_cache/. ``` @@ -147,9 +136,7 @@ a. show call graph in txt mode b. show call graph in gui mode $ python report.py -g c. show samples in source code - $ gvim annotate.config - change source_dirs line to: source_dirs = ["../demo/SimpleperfExampleOfKotlin"] - $ python annotate.py + $ python annotate.py -s ../demo/SimpleperfExampleOfKotlin $ find . -name "MainActivity.kt" | xargs gvim check the annoated source file MainActivity.kt. ``` diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp index e0027fc6..bf5e663e 100644 --- a/simpleperf/environment.cpp +++ b/simpleperf/environment.cpp @@ -38,6 +38,7 @@ #include <sys/system_properties.h> #endif +#include "event_type.h" #include "IOEventLoop.h" #include "read_elf.h" #include "thread_tree.h" @@ -352,6 +353,11 @@ static bool ReadPerfEventParanoid(int* value) { return true; } +bool CanRecordRawData() { + int value; + return IsRoot() || (ReadPerfEventParanoid(&value) && value == -1); +} + static const char* GetLimitLevelDescription(int limit_level) { switch (limit_level) { case -1: return "unlimited"; @@ -541,16 +547,34 @@ int WaitForAppProcess(const std::string& package_name) { } } +class ScopedFile { + public: + ScopedFile(const std::string& filepath, std::string app_package_name = "") + : filepath_(filepath), app_package_name_(app_package_name) {} + + ~ScopedFile() { + if (app_package_name_.empty()) { + unlink(filepath_.c_str()); + } else { + Workload::RunCmd({"run-as", app_package_name_, "rm", "-rf", filepath_}); + } + } + + private: + std::string filepath_; + std::string app_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) { + const std::string& output_filepath, bool need_tracepoint_events) { // 1. Test if the package exists. if (!Workload::RunCmd({"run-as", app_package_name, "echo", ">/dev/null"}, false)) { LOG(ERROR) << "Package " << app_package_name << "doesn't exist or isn't debuggable."; return false; } - // 2. Copy simpleperf binary to the package. + // 2. Copy simpleperf binary to the package. Create tracepoint_file if needed. std::string simpleperf_path; if (!android::base::Readlink("/proc/self/exe", &simpleperf_path)) { PLOG(ERROR) << "ReadLink failed"; @@ -559,12 +583,29 @@ bool RunInAppContext(const std::string& app_package_name, const std::string& cmd if (!Workload::RunCmd({"run-as", app_package_name, "cp", simpleperf_path, "simpleperf"})) { return false; } + ScopedFile scoped_simpleperf("simpleperf", app_package_name); + std::unique_ptr<ScopedFile> scoped_tracepoint_file; + const std::string tracepoint_file = "/data/local/tmp/tracepoint_events"; + if (need_tracepoint_events) { + // Since we can't read tracepoint events from tracefs in app's context, we need to prepare + // them in tracepoint_file in shell's context, and pass the path of tracepoint_file to the + // child process using --tracepoint-events option. + if (!android::base::WriteStringToFile(GetTracepointEvents(), tracepoint_file)) { + PLOG(ERROR) << "Failed to store tracepoint events"; + return false; + } + scoped_tracepoint_file.reset(new ScopedFile(tracepoint_file)); + } // 3. Prepare to start child process to profile. std::string output_basename = output_filepath.empty() ? "" : android::base::Basename(output_filepath); std::vector<std::string> new_args = {"run-as", app_package_name, "./simpleperf", cmd, "--in-app"}; + if (need_tracepoint_events) { + new_args.push_back("--tracepoint-events"); + new_args.push_back(tracepoint_file); + } for (size_t i = 0; i < args.size(); ++i) { if (i >= args.size() - workload_args_size || args[i] != "-o") { new_args.push_back(args[i]); diff --git a/simpleperf/environment.h b/simpleperf/environment.h index 603c9e60..b1c52ea0 100644 --- a/simpleperf/environment.h +++ b/simpleperf/environment.h @@ -72,6 +72,7 @@ bool CheckPerfEventLimit(); bool GetMaxSampleFrequency(uint64_t* max_sample_freq); bool CheckSampleFrequency(uint64_t sample_freq); bool CheckKernelSymbolAddresses(); +bool CanRecordRawData(); #if defined(__linux__) static inline uint64_t GetSystemClock() { @@ -94,7 +95,7 @@ void PrepareVdsoFile(); int WaitForAppProcess(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); + const std::string& output_filepath, bool need_tracepoint_events); // Below two functions are only used in cts tests, to force stat/record cmd to run in app's context. void SetDefaultAppPackageName(const std::string& package_name); diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp index 19364489..7c58081e 100644 --- a/simpleperf/event_attr.cpp +++ b/simpleperf/event_attr.cpp @@ -23,6 +23,7 @@ #include <android-base/logging.h> +#include "environment.h" #include "event_type.h" #include "utils.h" @@ -92,7 +93,9 @@ perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type) { if (attr.type == PERF_TYPE_TRACEPOINT) { // Tracepoint information are stored in raw data in sample records. - attr.sample_type |= PERF_SAMPLE_RAW; + if (CanRecordRawData()) { + attr.sample_type |= PERF_SAMPLE_RAW; + } } return attr; } diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp index a69067d5..bc639d12 100644 --- a/simpleperf/event_type.cpp +++ b/simpleperf/event_type.cpp @@ -16,6 +16,7 @@ #include "event_type.h" +#include <inttypes.h> #include <unistd.h> #include <algorithm> #include <string> @@ -23,6 +24,8 @@ #include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/stringprintf.h> #include <android-base/strings.h> #include "event_attr.h" @@ -35,12 +38,42 @@ static const std::vector<EventType> static_event_type_array = { #include "event_type_table.h" }; -static const std::vector<EventType> GetTracepointEventTypes() { +static std::string tracepoint_events; + +bool SetTracepointEventsFilePath(const std::string& filepath) { + if (!android::base::ReadFileToString(filepath, &tracepoint_events)) { + PLOG(ERROR) << "Failed to read " << filepath; + return false; + } + return true; +} + +std::string GetTracepointEvents() { + std::string result; + for (const EventType& event : GetAllEventTypes()) { + if (!result.empty()) { + result.push_back('\n'); + } + result += android::base::StringPrintf("%s %" PRIu64, event.name.c_str(), event.config); + } + return result; +} + +static std::vector<EventType> GetTracepointEventTypesFromString(const std::string& s) { std::vector<EventType> result; - if (!IsRoot()) { - // Not having permission to profile tracing events. - return result; + for (auto& line : android::base::Split(s, "\n")) { + std::vector<std::string> items = android::base::Split(line, " "); + CHECK_EQ(items.size(), 2u); + std::string event_name = items[0]; + uint64_t id; + CHECK(android::base::ParseUint(items[1].c_str(), &id)); + result.push_back(EventType(event_name, PERF_TYPE_TRACEPOINT, id, "", "")); } + return result; +} + +static std::vector<EventType> GetTracepointEventTypesFromTraceFs() { + std::vector<EventType> result; const std::string tracepoint_dirname = "/sys/kernel/debug/tracing/events"; for (const auto& system_name : GetSubDirs(tracepoint_dirname)) { std::string system_path = tracepoint_dirname + "/" + system_name; @@ -59,6 +92,16 @@ static const std::vector<EventType> GetTracepointEventTypes() { result.push_back(EventType(system_name + ":" + event_name, PERF_TYPE_TRACEPOINT, id, "", "")); } } + return result; +} + +static std::vector<EventType> GetTracepointEventTypes() { + std::vector<EventType> result; + if (!tracepoint_events.empty()) { + result = GetTracepointEventTypesFromString(tracepoint_events); + } else { + result = GetTracepointEventTypesFromTraceFs(); + } std::sort(result.begin(), result.end(), [](const EventType& type1, const EventType& type2) { return type1.name < type2.name; }); return result; @@ -69,7 +112,7 @@ const std::vector<EventType>& GetAllEventTypes() { if (event_type_array.empty()) { event_type_array.insert(event_type_array.end(), static_event_type_array.begin(), static_event_type_array.end()); - const std::vector<EventType> tracepoint_array = GetTracepointEventTypes(); + std::vector<EventType> tracepoint_array = GetTracepointEventTypes(); event_type_array.insert(event_type_array.end(), tracepoint_array.begin(), tracepoint_array.end()); } diff --git a/simpleperf/event_type.h b/simpleperf/event_type.h index a804b083..6ec544d8 100644 --- a/simpleperf/event_type.h +++ b/simpleperf/event_type.h @@ -52,6 +52,8 @@ struct EventType { std::string limited_arch; }; +bool SetTracepointEventsFilePath(const std::string& filepath); +std::string GetTracepointEvents(); const std::vector<EventType>& GetAllEventTypes(); const EventType* FindEventTypeByName(const std::string& name); diff --git a/simpleperf/inferno/inferno.py b/simpleperf/inferno/inferno.py index ade3e741..03cdf658 100644 --- a/simpleperf/inferno/inferno.py +++ b/simpleperf/inferno/inferno.py @@ -63,25 +63,33 @@ def collect_data(adb_client, process): unwinding_parameter = "--call-graph fp" print "Unwinding with frame pointers." + # Check whether sampling will be frequency based or event based. + sampling_parameter = "-f %s" % process.args.sample_frequency + if process.args.events: + tokens = process.args.events.split(" ") + if len(tokens) == 2: + num_events = tokens[0] + event_name = tokens[1] + sampling_parameter = "-c %s -e '%s'" % (num_events, event_name) + else: + print "Event format string not recognized. Expected \"requency event_name\"." + print "Got : [" + ",".join(tokens) + "]" + return False + print "Using event sampling (%s)." % sampling_parameter + else: + print "Using frequency sampling (%s)." % sampling_parameter + process.cmd = "./simpleperf record \ -o /data/local/tmp/perf.data \ %s \ -p %s \ --duration %s \ - -f %s" % ( + %s" % ( unwinding_parameter, process.pid, process.args.capture_duration, - process.args.sample_frequency) - - # TODO Add arg to configure what events to listen on: - # -e instructions - # -e cpu-cycles - # -e cache-references - # -e cache-misses - # -e branch-instructions - # -e branch-misses - # Also add the granularity with -c 100000 + sampling_parameter) + print("Process '%s' PID = %d" % (process.name, process.pid)) if process.args.skip_collection: @@ -257,8 +265,15 @@ def main(): parser.add_argument('-sc','--skip_collection', default=False, help='Skip data collection', action="store_true") parser.add_argument('-f', '--sample_frequency', default=6000, help='Sample frequency') parser.add_argument('-w', '--svg_width', type=int, default=1124) - parser.add_argument('-sb', '--skip_push_binary', help='Skip pushing simpleperf before profiling', default=False, action="store_true") - parser.add_argument('-du', '--dwarf_unwinding', help='Perform unwinding using dwarf instead of fp.', default=False, action='store_true') + parser.add_argument('-sb', '--skip_push_binary', help='Skip pushing simpleperf before profiling', + default=False, action="store_true") + parser.add_argument('-du', '--dwarf_unwinding', help='Perform unwinding using dwarf instead of fp.', + default=False, action='store_true') + parser.add_argument('-e', '--events', + help='Sample based on event occurences instead of frequency. ' + 'Format expected is "event_counts event_name". e.g: "10000 cpu-cyles". A few examples of \ + nmames: cpu-cycles, cache-references, cache-misses, branch-instructions, branch-misses', + default="") args = parser.parse_args() # Since we may attempt to sample privileged process, let's try to be root. diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp index f132dd92..8c245f15 100644 --- a/simpleperf/nonlinux_support/nonlinux_support.cpp +++ b/simpleperf/nonlinux_support/nonlinux_support.cpp @@ -28,3 +28,7 @@ std::vector<uint64_t> UnwindCallChain(int, const ThreadEntry&, const RegSet&, bool GetKernelBuildId(BuildId*) { return false; } + +bool CanRecordRawData() { + return false; +} diff --git a/simpleperf/scripts/annotate.config b/simpleperf/scripts/annotate.config deleted file mode 100644 index 2e5db555..00000000 --- a/simpleperf/scripts/annotate.config +++ /dev/null @@ -1,40 +0,0 @@ -# This configuration is written in python and used by annotate.py. - -import os - -# A list of profiling record files. By default it only contains perf.data. -perf_data_list = ["perf.data"] - - -# Directory used to read binaries with debug info. Ideally, it should be -# set to the path of binary_cache_dir collected by app_profiler.py. -# Set to "" if not available. -symfs_dir = "binary_cache" - - -# File path used to find kernel symbols. Set to "" if not available. -kallsyms = "" - - -# A list of directories used to find source files. -source_dirs = [] - - -# Directory used to output annotated source files. -annotate_dest_dir = "annotated_files" - - -# Sample Filters -# Use samples only in threads with selected names. -comm_filters = [] -# Use samples only in processes with selected process ids. -pid_filters = [] -# Use samples only in threads with selected thread ids. -tid_filters = [] -# Use samples only in selected binaries. -dso_filters = [] - - -# We use addr2line to map virtual address to source file and source line. -# So set the path to addr2line here. -addr2line_path = "addr2line"
\ No newline at end of file diff --git a/simpleperf/scripts/annotate.py b/simpleperf/scripts/annotate.py index 2b253bd7..27414ac6 100644 --- a/simpleperf/scripts/annotate.py +++ b/simpleperf/scripts/annotate.py @@ -57,7 +57,12 @@ class Addr2Line(object): """ def __init__(self, addr2line_path, symfs_dir=None): self.dso_dict = dict() - self.addr2line_path = addr2line_path + if addr2line_path and is_executable_available(addr2line_path): + self.addr2line_path = addr2line_path + else: + self.addr2line_path = find_tool_path('addr2line') + if not self.addr2line_path: + log_exit("Can't find addr2line.") self.symfs_dir = symfs_dir @@ -267,27 +272,28 @@ class SourceFileAnnotator(object): """group code for annotating source files""" def __init__(self, config): # check config variables - config_names = ['perf_data_list', 'symfs_dir', 'source_dirs', - 'annotate_dest_dir', 'comm_filters', 'pid_filters', - 'tid_filters', 'dso_filters', 'addr2line_path'] + config_names = ['perf_data_list', 'source_dirs', 'comm_filters', + 'pid_filters', 'tid_filters', 'dso_filters', 'addr2line_path'] for name in config_names: if name not in config: - log_fatal('config [%s] is missing' % name) - symfs_dir = config['symfs_dir'] - if symfs_dir and not os.path.isdir(symfs_dir): - log_fatal('[symfs_dir] "%s" is not a dir' % symfs_dir) - kallsyms = config['kallsyms'] - if kallsyms and not os.path.isfile(kallsyms): - log_fatal('[kallsyms] "%s" is not a file' % kallsyms) + log_exit('config [%s] is missing' % name) + symfs_dir = 'binary_cache' + if not os.path.isdir(symfs_dir): + symfs_dir = None + kallsyms = 'binary_cache/kallsyms' + if not os.path.isfile(kallsyms): + kallsyms = None source_dirs = config['source_dirs'] for dir in source_dirs: if not os.path.isdir(dir): - log_fatal('[source_dirs] "%s" is not a dir' % dir) + log_exit('[source_dirs] "%s" is not a dir' % dir) + if not config['source_dirs']: + log_exit('Please set source directories.') # init member variables self.config = config - self.symfs_dir = config.get('symfs_dir') - self.kallsyms = config.get('kallsyms') + self.symfs_dir = symfs_dir + self.kallsyms = kallsyms self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None if config.get('pid_filters'): self.pid_filter = {int(x) for x in config['pid_filters']} @@ -299,6 +305,7 @@ class SourceFileAnnotator(object): self.tid_filter = None self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None + config['annotate_dest_dir'] = 'annotated_files' output_dir = config['annotate_dest_dir'] if os.path.isdir(output_dir): shutil.rmtree(output_dir) @@ -623,14 +630,41 @@ class SourceFileAnnotator(object): wf.write(annotate) wf.write(lines[line-1]) +def main(): + parser = argparse.ArgumentParser(description= +"""Annotate source files based on profiling data. It reads line information from +binary_cache generated by app_profiler.py or binary_cache_builder.py, and +generate annotated source files in annotated_files directory.""") + parser.add_argument('-i', '--perf_data_list', nargs='+', action='append', help= +"""The paths of profiling data. Default is perf.data.""") + parser.add_argument('-s', '--source_dirs', nargs='+', action='append', help= +"""Directories to find source files.""") + parser.add_argument('--comm', nargs='+', action='append', help= +"""Use samples only in threads with selected names.""") + parser.add_argument('--pid', nargs='+', action='append', help= +"""Use samples only in processes with selected process ids.""") + parser.add_argument('--tid', nargs='+', action='append', help= +"""Use samples only in threads with selected thread ids.""") + parser.add_argument('--dso', nargs='+', action='append', help= +"""Use samples only in selected binaries.""") + parser.add_argument('--addr2line', help= +"""Set the path of addr2line.""") -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Annotate based on perf.data. See configurations in annotate.config.') - parser.add_argument('--config', default='annotate.config', - help='Set configuration file. Default is annotate.config.') args = parser.parse_args() - config = load_config(args.config) + config = {} + config['perf_data_list'] = flatten_arg_list(args.perf_data_list) + if not config['perf_data_list']: + config['perf_data_list'].append('perf.data') + config['source_dirs'] = flatten_arg_list(args.source_dirs) + config['comm_filters'] = flatten_arg_list(args.comm) + config['pid_filters'] = flatten_arg_list(args.pid) + config['tid_filters'] = flatten_arg_list(args.tid) + config['dso_filters'] = flatten_arg_list(args.dso) + config['addr2line_path'] = args.addr2line + annotator = SourceFileAnnotator(config) annotator.annotate() log_info('annotate finish successfully, please check result in annotated_files/.') + +if __name__ == '__main__': + main() diff --git a/simpleperf/scripts/app_profiler.config b/simpleperf/scripts/app_profiler.config index 752b85f7..9102cf0c 100644 --- a/simpleperf/scripts/app_profiler.config +++ b/simpleperf/scripts/app_profiler.config @@ -4,7 +4,7 @@ import os import os.path # The name of the android package, like com.example.android. -app_package_name = "com.example.android" +app_package_name = "" # Path of android studio project. It is used to find debug version of native shared libraries. @@ -43,11 +43,6 @@ launch_activity = '.MainActivity' launch_inst_test = '' -if recompile_app and not launch_activity and not launch_inst_test: - raise Exception('one of [launch_activity or launch_inst_test] is' - + 'needed for [recompile_app] to take effect.') - - # Profiling record options that will be passed directly to `simpleperf record` command on device. # You can set how long to profile using "--duration" option, or use Ctrl-C to stop profiling. record_options = "-e cpu-cycles:u -f 4000 -g --duration 10" @@ -57,15 +52,6 @@ record_options = "-e cpu-cycles:u -f 4000 -g --duration 10" perf_data_path = "perf.data" -# The path of adb. -adb_path = "adb" - - -# The path of readelf, used to read build id of files in binary cache. -# Set to "" if not available. -readelf_path = "readelf" - - -# binary_cache_dir is used to cache binaries pulled from device. To report precisely, we pull each -# binary hit by perf.data on host. -binary_cache_dir = "binary_cache" +# Collect binaries used in profiling data from device to binary_cache directory. +# It can be used to annotate source code. +collect_binaries = True diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py index e2a137f6..683d6b42 100644 --- a/simpleperf/scripts/app_profiler.py +++ b/simpleperf/scripts/app_profiler.py @@ -43,27 +43,34 @@ class AppProfiler(object): 3. Collect profiling data. """ def __init__(self, config): - # check config variables + self.check_config(config) + self.config = config + self.adb = AdbHelper() + self.is_root_device = False + self.android_version = 0 + self.device_arch = None + self.app_arch = None + self.app_pid = None + + + def check_config(self, config): config_names = ['app_package_name', 'native_lib_dir', 'apk_file_path', 'recompile_app', 'launch_activity', 'launch_inst_test', - 'record_options', 'perf_data_path', 'adb_path', 'readelf_path', - 'binary_cache_dir'] + 'record_options', 'perf_data_path'] for name in config_names: if name not in config: - log_fatal('config [%s] is missing' % name) + log_exit('config [%s] is missing' % name) + if not config['app_package_name']: + log_exit("The package name of the application hasn't been set") native_lib_dir = config.get('native_lib_dir') if native_lib_dir and not os.path.isdir(native_lib_dir): - log_fatal('[native_lib_dir] "%s" is not a dir' % native_lib_dir) + log_exit('[native_lib_dir] "%s" is not a dir' % native_lib_dir) apk_file_path = config.get('apk_file_path') if apk_file_path and not os.path.isfile(apk_file_path): - log_fatal('[apk_file_path] "%s" is not a file' % apk_file_path) - self.config = config - self.adb = AdbHelper(self.config['adb_path']) - self.is_root_device = False - self.android_version = 0 - self.device_arch = None - self.app_arch = None - self.app_pid = None + log_exit('[apk_file_path] "%s" is not a file' % apk_file_path) + if config['recompile_app']: + if not config['launch_activity'] and not config['launch_inst_test']: + log_exit('one of launch_activity and launch_inst_test is needed for recompile app') def profile(self): @@ -159,13 +166,13 @@ class AppProfiler(object): activity = self.config['app_package_name'] + '/' + self.config['launch_activity'] result = self.adb.run(['shell', 'am', 'start', '-n', activity]) if not result: - log_fatal("Can't start activity %s" % activity) + log_exit("Can't start activity %s" % activity) else: runner = self.config['app_package_name'] + '/android.support.test.runner.AndroidJUnitRunner' result = self.adb.run(['shell', 'am', 'instrument', '-e', 'class', self.config['launch_inst_test'], runner]) if not result: - log_fatal("Can't start instrumentation test %s" % self.config['launch_inst_test']) + log_exit("Can't start instrumentation test %s" % self.config['launch_inst_test']) for i in range(10): pid = self._find_app_process() @@ -173,7 +180,7 @@ class AppProfiler(object): return time.sleep(1) log_info('Wait for the app process for %d seconds' % (i + 1)) - log_fatal("Can't find the app process") + log_exit("Can't find the app process") def _find_app_process(self): @@ -192,7 +199,7 @@ class AppProfiler(object): def _get_app_environment(self): self.app_pid = self._find_app_process() if self.app_pid is None: - log_fatal("can't find process for app [%s]" % self.config['app_package_name']) + log_exit("can't find process for app [%s]" % self.config['app_package_name']) if self.device_arch in ['aarch64', 'x86_64']: output = self.run_in_app_dir(['cat', '/proc/%d/maps' % self.app_pid]) if output.find('linker64') != -1: @@ -300,12 +307,14 @@ class AppProfiler(object): self.run_in_app_dir(['cat perf.data | tee /data/local/tmp/perf.data >/dev/null']) self.adb.check_run_and_return_output(['pull', '/data/local/tmp/perf.data', self.config['perf_data_path']]) - config = copy.copy(self.config) - config['symfs_dirs'] = [] - if self.config['native_lib_dir']: - config['symfs_dirs'].append(self.config['native_lib_dir']) - binary_cache_builder = BinaryCacheBuilder(config) - binary_cache_builder.build_binary_cache() + if self.config['collect_binaries']: + config = copy.copy(self.config) + config['binary_cache_dir'] = 'binary_cache' + config['symfs_dirs'] = [] + if self.config['native_lib_dir']: + config['symfs_dirs'].append(self.config['native_lib_dir']) + binary_cache_builder = BinaryCacheBuilder(config) + binary_cache_builder.build_binary_cache() def run_in_app_dir(self, args, stdout_file=None, check_result=True): @@ -323,13 +332,62 @@ class AppProfiler(object): else: return ['shell', 'run-as', self.config['app_package_name']] + args - -if __name__ == '__main__': +def main(): parser = argparse.ArgumentParser( - description='Profile an android app. See configurations in app_profiler.config.') - parser.add_argument('--config', default='app_profiler.config', - help='Set configuration file. Default is app_profiler.config.') + description= +"""Profile an android app. See configurations in app_profiler.config.""") + parser.add_argument('--config', default='app_profiler.config', help= +"""Set configuration file. Default is app_profiler.config. The configurations +can be overridden by options in cmdline.""") + parser.add_argument('-p', '--package_name', help= +"""The package name of the profiled Android app.""") + parser.add_argument('-lib', '--native_lib_dir', help= +"""Path to find debug version of native shared libraries used in the app.""") + parser.add_argument('-nc', '--skip_recompile', action='store_true', help= +"""By default we recompile java bytecode to native instructions to profile java +code. It takes some time. You can skip it if the code has been compiled or you +don't need to profile java code.""") + parser.add_argument('--apk', help= +"""Apk file of the profiled app, used on Android version <= M, which needs to +reinstall the app to recompile it.""") + parser.add_argument('-a', '--activity', help= +"""Start an activity before profiling. It can be used to profile the startup +time of an activity. Default is .MainActivity.""") + parser.add_argument('-t', '--test', help= +"""Start an instrumentation test before profiling. It can be used to profile +an instrumentation test.""") + parser.add_argument('-r', '--record_options', help= +"""Set options for `simpleperf record` command. Default is "-e cpu-cycles:u -f 4000 -g --duration 10".""") + parser.add_argument('-o', '--perf_data_path', help= +"""The path to store profiling data. Default is perf.data.""") + parser.add_argument('-nb', '--skip_collect_binaries', action='store_true', help= +"""By default we collect binaries used in profiling data from device to +binary_cache directory. It can be used to annotate source code. This option skips it.""") args = parser.parse_args() config = load_config(args.config) + if args.package_name: + config['app_package_name'] = args.package_name + if args.native_lib_dir: + config['native_lib_dir'] = args.native_lib_dir + if args.skip_recompile: + config['recompile_app'] = False + if args.apk: + config['apk'] = args.apk + if args.activity: + config['launch_activity'] = args.activity + config['launch_inst_test'] = None + if args.test: + config['launch_inst_test'] = args.test + config['launch_activity'] = None + if args.record_options: + config['record_options'] = args.record_options + if args.perf_data_path: + config['perf_data_path'] = args.perf_data_path + if args.skip_collect_binaries: + config['collect_binaries'] = False + profiler = AppProfiler(config) profiler.profile() + +if __name__ == '__main__': + main()
\ No newline at end of file diff --git a/simpleperf/scripts/binary_cache_builder.config b/simpleperf/scripts/binary_cache_builder.config deleted file mode 100644 index 49cc5ae7..00000000 --- a/simpleperf/scripts/binary_cache_builder.config +++ /dev/null @@ -1,27 +0,0 @@ -# This configuration is written in python and used by binary_cache_builder.py. - -import os -import os.path - -# path of profiling record data. -perf_data_path = "perf.data" - - -# directories to find binaries with symbols and debug information. -# If binaries are found in any of these directories, having the same build_id -# as the one recorded in perf.data, then we copy the binary in the directory -# instead of pulling the binary from device. -symfs_dirs = [] - - -# directory to cache binaries. To report precisely, we pull needed binaries -# to host. However, We don't need to pull a binary if there is already a binary -# in binary_cache_dir having the same build_id as the one on device. -binary_cache_dir = "binary_cache" - - -# path of adb. -adb_path = "adb" - -# path of readelf, set to "" if not available. -readelf_path = "readelf"
\ No newline at end of file diff --git a/simpleperf/scripts/binary_cache_builder.py b/simpleperf/scripts/binary_cache_builder.py index 950e2fb7..4ce4a320 100644 --- a/simpleperf/scripts/binary_cache_builder.py +++ b/simpleperf/scripts/binary_cache_builder.py @@ -36,22 +36,23 @@ from utils import * class BinaryCacheBuilder(object): """Collect all binaries needed by perf.data in binary_cache.""" def __init__(self, config): - config_names = ['perf_data_path', 'symfs_dirs', 'adb_path', - 'readelf_path', 'binary_cache_dir'] + config_names = ['perf_data_path', 'symfs_dirs'] for name in config_names: if name not in config: - log_fatal('config for "%s" is missing' % name) + log_exit('config for "%s" is missing' % name) self.perf_data_path = config.get('perf_data_path') if not os.path.isfile(self.perf_data_path): - log_fatal("can't find file %s" % self.perf_data_path) + log_exit("can't find file %s" % self.perf_data_path) self.symfs_dirs = config.get('symfs_dirs') for symfs_dir in self.symfs_dirs: if not os.path.isdir(symfs_dir): - log_fatal("symfs_dir '%s' is not a directory" % symfs_dir) - self.adb = AdbHelper(config['adb_path']) - self.readelf_path = config['readelf_path'] - self.binary_cache_dir = config['binary_cache_dir'] + log_exit("symfs_dir '%s' is not a directory" % symfs_dir) + self.adb = AdbHelper() + self.readelf_path = find_tool_path('readelf') + if not self.readelf_path and self.symfs_dirs: + log_warning("Debug shared libraries on host are not used because can't find readelf.") + self.binary_cache_dir = 'binary_cache' if not os.path.isdir(self.binary_cache_dir): os.makedirs(self.binary_cache_dir) @@ -227,12 +228,22 @@ class BinaryCacheBuilder(object): self.adb.run(['pull', '/proc/kallsyms', file]) -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description="Pull binaries needed by perf.data from device to binary_cache.") - parser.add_argument('--config', default='binary_cache_builder.config', - help='Set configuration file. Default is binary_cache_builder.config.') +def main(): + parser = argparse.ArgumentParser(description= +"""Pull binaries needed by perf.data from device to binary_cache directory.""") + parser.add_argument('-i', '--perf_data_path', default='perf.data', help= +"""The path of profiling data.""") + parser.add_argument('-lib', '--native_lib_dir', nargs='+', help= +"""Path to find debug version of native shared libraries used in the app.""", + action='append') args = parser.parse_args() - config = load_config(args.config) + config = {} + config['perf_data_path'] = args.perf_data_path + config['symfs_dirs'] = flatten_arg_list(args.native_lib_dir) + builder = BinaryCacheBuilder(config) - builder.build_binary_cache()
\ No newline at end of file + builder.build_binary_cache() + + +if __name__ == '__main__': + main()
\ No newline at end of file diff --git a/simpleperf/scripts/pprof_proto_generator.config b/simpleperf/scripts/pprof_proto_generator.config deleted file mode 100644 index aa82b92c..00000000 --- a/simpleperf/scripts/pprof_proto_generator.config +++ /dev/null @@ -1,39 +0,0 @@ -# This configuration is written in python and used by binary_cache_builder.py. - -import os -import os.path - -# path of profiling record data. -perf_data_path = "perf.data" - -# output path. -output_file = "pprof.profile" - - -# directory to cache binaries with symbols and debug information. -# Can be generated by binary_cache_builder.py. -binary_cache_dir = "binary_cache" - - -# path to find kernel symbols. -kallsyms = "" - - -if binary_cache_dir: - path = os.path.join(binary_cache_dir, 'kallsyms') - if os.path.isfile(path): - kallsyms = path - -# Sample Filters -# Use samples only in threads with selected names. -comm_filters = [] -# Use samples only in processes with selected process ids. -pid_filters = [] -# Use samples only in threads with selected thread ids. -tid_filters = [] -# Use samples only in selected binaries. -dso_filters = [] - -# We use addr2line to map virtual address to source file and source line. -# So set the path to addr2line here. -addr2line_path = "addr2line"
\ No newline at end of file diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py index fa2fdb11..ad519110 100644 --- a/simpleperf/scripts/pprof_proto_generator.py +++ b/simpleperf/scripts/pprof_proto_generator.py @@ -255,15 +255,16 @@ class PprofProfileGenerator(object): self.config = config self.lib = ReportLib() - if config.get('binary_cache_dir'): - if not os.path.isdir(config.get('binary_cache_dir')): - config['binary_cache_dir'] = '' - else: - self.lib.SetSymfs(config['binary_cache_dir']) + config['binary_cache_dir'] = 'binary_cache' + if not os.path.isdir(config['binary_cache_dir']): + config['binary_cache_dir'] = None + else: + self.lib.SetSymfs(config['binary_cache_dir']) if config.get('record_file'): self.lib.SetRecordFile(config['record_file']) - if config.get('kallsyms'): - self.lib.SetKallsymsFile(config['kallsyms']) + kallsyms = 'binary_cache/kallsyms' + if os.path.isfile(kallsyms): + self.lib.SetKallsymsFile(kallsyms) self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None if config.get('pid_filters'): self.pid_filter = {int(x) for x in config['pid_filters']} @@ -318,8 +319,7 @@ class PprofProfileGenerator(object): self.add_sample(sample) # 2. Generate line info for locations and functions. - if self.config.get('binary_cache_dir'): - self.gen_source_lines() + self.gen_source_lines() # 3. Produce samples/locations/functions in profile for sample in self.sample_list: @@ -446,6 +446,15 @@ class PprofProfileGenerator(object): def gen_source_lines(self): # 1. Create Addr2line instance + if not self.config.get('binary_cache_dir'): + log_info("Can't generate line information because binary_cache is missing.") + return + if not self.config['addr2line_path'] or not is_executable_available( + self.config['addr2line_path']): + if not find_tool_path('addr2line'): + log_info("Can't generate line information because can't find addr2line.") + return + addr2line = Addr2Line(self.config['addr2line_path'], self.config['binary_cache_dir']) # 2. Put all needed addresses to it. @@ -542,16 +551,38 @@ class PprofProfileGenerator(object): def main(): parser = argparse.ArgumentParser(description='Generate pprof profile data in pprof.profile.') - parser.add_argument('--show', nargs=1, help='print existing profile.pprof') - parser.add_argument('--config', nargs=1, default='pprof_proto_generator.config', - help='Set config file, default is gen_pprof_proto.config.') - args = parser.parse_args(sys.argv[1:]) + parser.add_argument('--show', nargs='?', action='append', help='print existing pprof.profile.') + parser.add_argument('-i', '--perf_data_path', default='perf.data', help= +"""The path of profiling data.""") + parser.add_argument('-o', '--output_file', default='pprof.profile', help= +"""The path of generated pprof profile data.""") + parser.add_argument('--comm', nargs='+', action='append', help= +"""Use samples only in threads with selected names.""") + parser.add_argument('--pid', nargs='+', action='append', help= +"""Use samples only in processes with selected process ids.""") + parser.add_argument('--tid', nargs='+', action='append', help= +"""Use samples only in threads with selected thread ids.""") + parser.add_argument('--dso', nargs='+', action='append', help= +"""Use samples only in selected binaries.""") + parser.add_argument('--addr2line', help= +"""Set the path of addr2line.""") + + args = parser.parse_args() if args.show: - profile = load_pprof_profile(args.show[0]) + show_file = args.show[0] if args.show[0] else 'pprof.profile' + profile = load_pprof_profile(show_file) printer = PprofProfilePrinter(profile) printer.show() return - config = load_config(args.config) + + config = {} + config['perf_data_path'] = args.perf_data_path + config['output_file'] = args.output_file + config['comm_filters'] = flatten_arg_list(args.comm) + config['pid_filters'] = flatten_arg_list(args.pid) + config['tid_filters'] = flatten_arg_list(args.tid) + config['dso_filters'] = flatten_arg_list(args.dso) + config['addr2line_path'] = args.addr2line generator = PprofProfileGenerator(config) profile = generator.gen() store_pprof_profile(config['output_file'], profile) diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/utils.py index a0b3b3ba..2e2c69ad 100644 --- a/simpleperf/scripts/utils.py +++ b/simpleperf/scripts/utils.py @@ -20,6 +20,7 @@ from __future__ import print_function import logging +import os import os.path import subprocess import sys @@ -31,6 +32,9 @@ def get_script_dir(): def is_windows(): return sys.platform == 'win32' or sys.platform == 'cygwin' +def is_darwin(): + return sys.platform == 'darwin' + def is_python3(): return sys.version_info >= (3, 0) @@ -50,6 +54,9 @@ def log_warning(msg): def log_fatal(msg): raise Exception(msg) +def log_exit(msg): + sys.exit(msg) + def str_to_bytes(str): if not is_python3(): return str @@ -95,8 +102,75 @@ def get_host_binary_path(binary_name): return binary_path +def is_executable_available(executable, option='--help'): + """ Run an executable to see if it exists. """ + try: + subproc = subprocess.Popen([executable, option], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + subproc.communicate() + return subproc.returncode == 0 + except: + return False + +expected_tool_paths = { + 'adb': { + 'test_option': 'version', + 'darwin': [(True, 'Library/Android/sdk/platform-tools/adb'), + (False, '../../platform-tools/adb')], + 'linux': [(True, 'Android/Sdk/platform-tools/adb'), + (False, '../../platform-tools/adb')], + 'windows': [(True, 'AppData/Local/Android/sdk/platform-tools/adb'), + (False, '../../platform-tools/adb')], + }, + 'readelf': { + 'test_option': '--help', + 'darwin': [(True, 'Library/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-readelf'), + (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-readelf')], + 'linux': [(True, 'Android/Sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-readelf'), + (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-readelf')], + 'windows': [(True, 'AppData/Local/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-readelf'), + (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-readelf')], + }, + 'addr2line': { + 'test_option': '--help', + 'darwin': [(True, 'Library/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line'), + (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line')], + 'linux': [(True, 'Android/Sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line'), + (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line')], + 'windows': [(True, 'AppData/Local/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line'), + (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line')], + }, +} + +def find_tool_path(toolname): + if toolname not in expected_tool_paths: + return None + test_option = expected_tool_paths[toolname]['test_option'] + if is_executable_available(toolname, test_option): + return toolname + platform = 'linux' + if is_windows(): + platform = 'windows' + elif is_darwin(): + platform = 'darwin' + paths = expected_tool_paths[toolname][platform] + home = os.environ.get('HOMEPATH') if is_windows() else os.environ.get('HOME') + for (relative_to_home, path) in paths: + path = path.replace('/', os.sep) + if relative_to_home: + path = os.path.join(home, path) + else: + path = os.path.join(get_script_dir(), path) + if is_executable_available(path, test_option): + return path + return None + + class AdbHelper(object): - def __init__(self, adb_path): + def __init__(self): + adb_path = find_tool_path('adb') + if not adb_path: + log_exit("Can't find adb in PATH environment.") self.adb_path = adb_path @@ -116,7 +190,7 @@ class AdbHelper(object): (stdoutdata, _) = subproc.communicate() returncode = subproc.returncode result = (returncode == 0) - if stdoutdata: + if stdoutdata and adb_args[1] != 'push' and adb_args[1] != 'pull': stdoutdata = bytes_to_str(stdoutdata) log_debug(stdoutdata) log_debug('run adb cmd: %s [result %s]' % (adb_args, result)) @@ -129,7 +203,7 @@ class AdbHelper(object): def check_run_and_return_output(self, adb_args, stdout_file=None): result, stdoutdata = self.run_and_return_output(adb_args, stdout_file) if not result: - log_fatal('run "adb %s" failed' % adb_args) + log_exit('run "adb %s" failed' % adb_args) return stdoutdata @@ -161,7 +235,7 @@ class AdbHelper(object): def load_config(config_file): if not os.path.exists(config_file): - log_fatal("can't find config_file: %s" % config_file) + log_exit("can't find config_file: %s" % config_file) config = {} if is_python3(): with open(config_file, 'r') as fh: @@ -172,4 +246,12 @@ def load_config(config_file): return config +def flatten_arg_list(arg_list): + res = [] + if arg_list: + for items in arg_list: + res += items + return res + + logging.getLogger().setLevel(logging.DEBUG) diff --git a/simpleperf/workload.cpp b/simpleperf/workload.cpp index 4aaeb2a9..60c9ed15 100644 --- a/simpleperf/workload.cpp +++ b/simpleperf/workload.cpp @@ -45,7 +45,7 @@ bool Workload::RunCmd(const std::vector<std::string>& args, bool report_error) { std::string arg_str = android::base::Join(args, ' '); int ret = system(arg_str.c_str()); if (ret != 0 && report_error) { - PLOG(ERROR) << "Failed to run cmd " << arg_str; + LOG(ERROR) << "Failed to run cmd " << arg_str << ", exitcode " << ret; return false; } return ret == 0; |