diff options
author | android-build-prod (mdb) <android-build-team-robot@google.com> | 2019-05-28 22:29:54 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2019-05-28 22:29:54 +0000 |
commit | d411055d38e567dcd73aa61de531cc947fbc0767 (patch) | |
tree | a88d9f2624e59a49eb7540306030fb8bca5bba14 | |
parent | 2201b3e358fc00a0c0a034fcf15be7d96dc05f9a (diff) | |
parent | bcc18a99d9a9b1dd4dd055f54088648361989091 (diff) | |
download | extras-platform-tools-29.0.1.tar.gz |
Merge "Snap for 5611628 from 16ed224e174c14760f15c896ac29db2a15ad7c77 to sdk-release" into sdk-releaseplatform-tools-29.0.1
-rw-r--r-- | partition_tools/lpdump.cc | 43 | ||||
-rw-r--r-- | simpleperf/cmd_record.cpp | 2 | ||||
-rwxr-xr-x | simpleperf/scripts/pprof_proto_generator.py | 5 | ||||
-rwxr-xr-x | simpleperf/scripts/report_html.py | 85 | ||||
-rw-r--r-- | simpleperf/scripts/script_testdata/aggregatable_perf1.data | bin | 0 -> 2202763 bytes | |||
-rw-r--r-- | simpleperf/scripts/script_testdata/aggregatable_perf2.data | bin | 0 -> 2024113 bytes | |||
-rwxr-xr-x | simpleperf/scripts/test.py | 69 | ||||
-rw-r--r-- | simpleperf/utils.cpp | 5 | ||||
-rw-r--r-- | toolchain-extras/profile-extras-test.cpp | 2 | ||||
-rw-r--r-- | toolchain-extras/profile-extras.cpp | 2 |
10 files changed, 202 insertions, 11 deletions
diff --git a/partition_tools/lpdump.cc b/partition_tools/lpdump.cc index 1b607f8a..3c1499e3 100644 --- a/partition_tools/lpdump.cc +++ b/partition_tools/lpdump.cc @@ -55,7 +55,12 @@ static int usage(int /* argc */, char* argv[], std::ostream& cerr) { "\n" "Options:\n" " -s, --slot=N Slot number or suffix.\n" - " -j, --json Print in JSON format.\n"; + " -j, --json Print in JSON format.\n" + " -e, --is-super-empty\n" + " The given file is a super_empty.img.\n" + " -d, --dump-metadata-size\n" + " Print the space reserved for metadata to stdout\n" + " in bytes.\n"; return EX_USAGE; } @@ -247,6 +252,13 @@ static int PrintJson(const LpMetadata* metadata, std::ostream& cout, return EX_OK; } +static int DumpMetadataSize(const LpMetadata* metadata, std::ostream& cout) { + auto super_device = GetMetadataSuperBlockDevice(*metadata); + uint64_t metadata_size = super_device->first_logical_sector * LP_SECTOR_SIZE; + cout << metadata_size << std::endl; + return EX_OK; +} + class FileOrBlockDeviceOpener final : public PartitionOpener { public: android::base::unique_fd Open(const std::string& path, int flags) const override { @@ -270,6 +282,8 @@ int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) { { "slot", required_argument, nullptr, 's' }, { "help", no_argument, nullptr, 'h' }, { "json", no_argument, nullptr, 'j' }, + { "dump-metadata-size", no_argument, nullptr, 'd' }, + { "is-super-empty", no_argument, nullptr, 'e' }, { nullptr, 0, nullptr, 0 }, }; // clang-format on @@ -281,18 +295,30 @@ int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) { int index; uint32_t slot = 0; bool json = false; - while ((rv = getopt_long_only(argc, argv, "s:jh", options, &index)) != -1) { + bool is_empty = false; + bool dump_metadata_size = false; + while ((rv = getopt_long_only(argc, argv, "s:jhde", options, &index)) != -1) { switch (rv) { case 'h': - return usage(argc, argv, cerr); + usage(argc, argv, cerr); + return EX_OK; case 's': if (!android::base::ParseUint(optarg, &slot)) { slot = SlotNumberForSlotSuffix(optarg); } break; + case 'e': + is_empty = true; + break; + case 'd': + dump_metadata_size = true; + break; case 'j': json = true; break; + case '?': + case ':': + return usage(argc, argv, cerr); } } @@ -300,12 +326,17 @@ int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) { if (optind < argc) { FileOrBlockDeviceOpener opener; const char* file = argv[optind++]; - pt = ReadMetadata(opener, file, slot); + if (!is_empty) { + pt = ReadMetadata(opener, file, slot); + } if (!pt && !IsBlockDevice(file)) { pt = ReadFromImageFile(file); } } else { #ifdef __ANDROID__ + if (is_empty) { + return usage(argc, argv, cerr); + } auto slot_number = SlotNumberForSlotSuffix(GetSlotSuffix()); pt = ReadMetadata(GetSuperPartionName(), slot_number); #else @@ -323,6 +354,10 @@ int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) { return EX_NOINPUT; } + if (dump_metadata_size) { + return DumpMetadataSize(pt.get(), cout); + } + cout << "Metadata version: " << pt->header.major_version << "." << pt->header.minor_version << "\n"; cout << "Metadata size: " << (pt->header.header_size + pt->header.tables_size) << " bytes\n"; diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index ff01d808..ab006a1c 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -514,7 +514,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) { } } if (stdio_controls_profiling_) { - if (!loop->AddReadEvent(0, [&]() { return ProcessControlCmd(loop); })) { + if (!loop->AddReadEvent(0, [this, loop]() { return ProcessControlCmd(loop); })) { return false; } } diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py index d0b4da95..35678b13 100755 --- a/simpleperf/scripts/pprof_proto_generator.py +++ b/simpleperf/scripts/pprof_proto_generator.py @@ -256,6 +256,8 @@ class PprofProfileGenerator(object): kallsyms = 'binary_cache/kallsyms' if os.path.isfile(kallsyms): self.lib.SetKallsymsFile(kallsyms) + if config.get('show_art_frames'): + self.lib.ShowArtFrames() 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']} @@ -561,6 +563,8 @@ def main(): parser.add_argument('--dso', nargs='+', action='append', help=""" Use samples only in selected binaries.""") parser.add_argument('--ndk_path', type=extant_dir, help='Set the path of a ndk release.') + parser.add_argument('--show_art_frames', action='store_true', + help='Show frames of internal methods in the ART Java interpreter.') args = parser.parse_args() if args.show: @@ -578,6 +582,7 @@ def main(): config['tid_filters'] = flatten_arg_list(args.tid) config['dso_filters'] = flatten_arg_list(args.dso) config['ndk_path'] = args.ndk_path + config['show_art_frames'] = args.show_art_frames generator = PprofProfileGenerator(config) profile = generator.gen() store_pprof_profile(config['output_file'], profile) diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py index 1a616fbc..d7fe0ecf 100755 --- a/simpleperf/scripts/report_html.py +++ b/simpleperf/scripts/report_html.py @@ -127,6 +127,20 @@ class ProcessScope(object): for thread in threads] return result + def merge_by_thread_name(self, process): + self.event_count += process.event_count + thread_list = list(self.threads.values()) + list(process.threads.values()) + new_threads = {} # map from thread name to ThreadScope + for thread in thread_list: + cur_thread = new_threads.get(thread.name) + if cur_thread is None: + new_threads[thread.name] = thread + else: + cur_thread.merge(thread) + self.threads = {} + for thread in new_threads.values(): + self.threads[thread.tid] = thread + class ThreadScope(object): @@ -200,6 +214,18 @@ class ThreadScope(object): result['rg'] = self.reverse_call_graph.gen_sample_info() return result + def merge(self, thread): + self.event_count += thread.event_count + self.sample_count += thread.sample_count + for lib_id, lib in thread.libs.items(): + cur_lib = self.libs.get(lib_id) + if cur_lib is None: + self.libs[lib_id] = lib + else: + cur_lib.merge(lib) + self.call_graph.merge(thread.call_graph) + self.reverse_call_graph.merge(thread.reverse_call_graph) + class LibScope(object): @@ -222,6 +248,15 @@ class LibScope(object): for func in self.functions.values()] return result + def merge(self, lib): + self.event_count += lib.event_count + for func_id, function in lib.functions.items(): + cur_function = self.functions.get(func_id) + if cur_function is None: + self.functions[func_id] = function + else: + cur_function.merge(function) + class FunctionScope(object): @@ -274,6 +309,28 @@ class FunctionScope(object): result['a'] = items return result + def merge(self, function): + self.sample_count += function.sample_count + self.event_count += function.event_count + self.subtree_event_count += function.subtree_event_count + self.addr_hit_map = self.__merge_hit_map(self.addr_hit_map, function.addr_hit_map) + self.line_hit_map = self.__merge_hit_map(self.line_hit_map, function.line_hit_map) + + @staticmethod + def __merge_hit_map(map1, map2): + if not map1: + return map2 + if not map2: + return map1 + for key, value2 in map2.items(): + value1 = map1.get(key) + if value1 is None: + map1[key] = value2 + else: + value1[0] += value2[0] + value1[1] += value2[1] + return map1 + class CallNode(object): @@ -315,6 +372,16 @@ class CallNode(object): result['c'] = [child.gen_sample_info() for child in self.children.values()] return result + def merge(self, node): + self.event_count += node.event_count + self.subtree_event_count += node.subtree_event_count + for key, child in node.children.items(): + cur_child = self.children.get(key) + if cur_child is None: + self.children[key] = child + else: + cur_child.merge(child) + class LibSet(object): """ Collection of shared libraries used in perf.data. """ @@ -558,6 +625,19 @@ class RecordData(object): for thread in event.threads: thread.update_subtree_event_count() + def aggregate_by_thread_name(self): + for event in self.events.values(): + new_processes = {} # from process name to ProcessScope + for process in event.processes.values(): + cur_process = new_processes.get(process.name) + if cur_process is None: + new_processes[process.name] = process + else: + cur_process.merge_by_thread_name(process) + event.processes = {} + for process in new_processes.values(): + event.processes[process.pid] = process + def limit_percents(self, min_func_percent, min_callchain_percent): hit_func_ids = set() for event in self.events.values(): @@ -833,6 +913,9 @@ def main(): parser.add_argument('--no_browser', action='store_true', help="Don't open report in browser.") parser.add_argument('--show_art_frames', action='store_true', help='Show frames of internal methods in the ART Java interpreter.') + parser.add_argument('--aggregate-by-thread-name', action='store_true', help="""aggregate + samples by thread name instead of thread id. This is useful for + showing multiple perf.data generated for the same app.""") args = parser.parse_args() # 1. Process args. @@ -854,6 +937,8 @@ def main(): record_data = RecordData(binary_cache_path, ndk_path, build_addr_hit_map) for record_file in args.record_file: record_data.load_record_file(record_file, args.show_art_frames) + if args.aggregate_by_thread_name: + record_data.aggregate_by_thread_name() record_data.limit_percents(args.min_func_percent, args.min_callchain_percent) def filter_lib(lib_name): diff --git a/simpleperf/scripts/script_testdata/aggregatable_perf1.data b/simpleperf/scripts/script_testdata/aggregatable_perf1.data Binary files differnew file mode 100644 index 00000000..61f5258a --- /dev/null +++ b/simpleperf/scripts/script_testdata/aggregatable_perf1.data diff --git a/simpleperf/scripts/script_testdata/aggregatable_perf2.data b/simpleperf/scripts/script_testdata/aggregatable_perf2.data Binary files differnew file mode 100644 index 00000000..7b8224e9 --- /dev/null +++ b/simpleperf/scripts/script_testdata/aggregatable_perf2.data diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py index 067c493a..5c696069 100755 --- a/simpleperf/scripts/test.py +++ b/simpleperf/scripts/test.py @@ -36,9 +36,11 @@ Test using both `adb root` and `adb unroot`. """ from __future__ import print_function import argparse +import collections import filecmp import fnmatch import inspect +import json import os import re import shutil @@ -1235,7 +1237,53 @@ class TestNativeLibDownloader(unittest.TestCase): class TestReportHtml(TestBase): def test_long_callchain(self): - self.run_cmd(['report_html.py', '-i', 'testdata/perf_with_long_callchain.data']) + self.run_cmd(['report_html.py', '-i', + os.path.join('testdata', 'perf_with_long_callchain.data')]) + + def test_aggregated_by_thread_name(self): + # Calculate event_count for each thread name before aggregation. + event_count_for_thread_name = collections.defaultdict(lambda: 0) + # use "--min_func_percent 0" to avoid cutting any thread. + self.run_cmd(['report_html.py', '--min_func_percent', '0', '-i', + os.path.join('testdata', 'aggregatable_perf1.data'), + os.path.join('testdata', 'aggregatable_perf2.data')]) + record_data = self._load_record_data_in_html('report.html') + event = record_data['sampleInfo'][0] + for process in event['processes']: + for thread in process['threads']: + thread_name = record_data['threadNames'][str(thread['tid'])] + event_count_for_thread_name[thread_name] += thread['eventCount'] + + # Check event count for each thread after aggregation. + self.run_cmd(['report_html.py', '--aggregate-by-thread-name', + '--min_func_percent', '0', '-i', + os.path.join('testdata', 'aggregatable_perf1.data'), + os.path.join('testdata', 'aggregatable_perf2.data')]) + record_data = self._load_record_data_in_html('report.html') + event = record_data['sampleInfo'][0] + hit_count = 0 + for process in event['processes']: + for thread in process['threads']: + thread_name = record_data['threadNames'][str(thread['tid'])] + self.assertEqual(thread['eventCount'], + event_count_for_thread_name[thread_name]) + hit_count += 1 + self.assertEqual(hit_count, len(event_count_for_thread_name)) + + def _load_record_data_in_html(self, html_file): + with open(html_file, 'r') as fh: + data = fh.read() + start_str = 'type="application/json"' + end_str = '</script>' + start_pos = data.find(start_str) + self.assertNotEqual(start_pos, -1) + start_pos = data.find('>', start_pos) + self.assertNotEqual(start_pos, -1) + start_pos += 1 + end_pos = data.find(end_str, start_pos) + self.assertNotEqual(end_pos, -1) + json_data = data[start_pos:end_pos] + return json.loads(json_data) class TestBinaryCacheBuilder(TestBase): @@ -1347,6 +1395,25 @@ class TestApiProfiler(TestBase): self.run_java_api_test('java_api-profile_Q.apk', 'Q') +class TestPprofProtoGenerator(TestBase): + def test_show_art_frames(self): + if not HAS_GOOGLE_PROTOBUF: + log_info('Skip test for pprof_proto_generator because google.protobuf is missing') + return + testdata_path = os.path.join('testdata', 'perf_with_interpreter_frames.data') + art_frame_str = 'art::interpreter::DoCall' + + # By default, don't show art frames. + self.run_cmd(['pprof_proto_generator.py', '-i', testdata_path]) + output = self.run_cmd(['pprof_proto_generator.py', '--show'], return_output=True) + self.assertEqual(output.find(art_frame_str), -1, 'output: ' + output) + + # Use --show_art_frames to show art frames. + self.run_cmd(['pprof_proto_generator.py', '-i', testdata_path, '--show_art_frames']) + output = self.run_cmd(['pprof_proto_generator.py', '--show'], return_output=True) + self.assertNotEqual(output.find(art_frame_str), -1, 'output: ' + output) + + def get_all_tests(): tests = [] for name, value in globals().items(): diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp index e3dc052b..b27fabd6 100644 --- a/simpleperf/utils.cpp +++ b/simpleperf/utils.cpp @@ -111,11 +111,10 @@ bool ArchiveHelper::IterateEntries( return false; } ZipEntry zentry; - ZipString zname; + std::string zname; int result; while ((result = Next(iteration_cookie, &zentry, &zname)) == 0) { - std::string name(zname.name, zname.name + zname.name_length); - if (!callback(zentry, name)) { + if (!callback(zentry, zname)) { break; } } diff --git a/toolchain-extras/profile-extras-test.cpp b/toolchain-extras/profile-extras-test.cpp index 0cc4cef8..5e48a646 100644 --- a/toolchain-extras/profile-extras-test.cpp +++ b/toolchain-extras/profile-extras-test.cpp @@ -28,7 +28,7 @@ void __gcov_flush() { } } -static const char kCoveragePropName[] = "coverage.flush"; +static const char kCoveragePropName[] = "debug.coverage.flush"; TEST(profile_extras, smoke) { flush_count = 0; diff --git a/toolchain-extras/profile-extras.cpp b/toolchain-extras/profile-extras.cpp index 3af46a10..1b1393b7 100644 --- a/toolchain-extras/profile-extras.cpp +++ b/toolchain-extras/profile-extras.cpp @@ -33,7 +33,7 @@ static void gcov_signal_handler(__unused int signum) { __gcov_flush(); } -static const char kCoveragePropName[] = "coverage.flush"; +static const char kCoveragePropName[] = "debug.coverage.flush"; // In a loop, wait for any change to sysprops and trigger a __gcov_flush when // <kCoveragePropName> sysprop transistions to "1" after a transistion to "0". |