summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-prod (mdb) <android-build-team-robot@google.com>2019-05-28 22:29:54 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2019-05-28 22:29:54 +0000
commitd411055d38e567dcd73aa61de531cc947fbc0767 (patch)
treea88d9f2624e59a49eb7540306030fb8bca5bba14
parent2201b3e358fc00a0c0a034fcf15be7d96dc05f9a (diff)
parentbcc18a99d9a9b1dd4dd055f54088648361989091 (diff)
downloadextras-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.cc43
-rw-r--r--simpleperf/cmd_record.cpp2
-rwxr-xr-xsimpleperf/scripts/pprof_proto_generator.py5
-rwxr-xr-xsimpleperf/scripts/report_html.py85
-rw-r--r--simpleperf/scripts/script_testdata/aggregatable_perf1.databin0 -> 2202763 bytes
-rw-r--r--simpleperf/scripts/script_testdata/aggregatable_perf2.databin0 -> 2024113 bytes
-rwxr-xr-xsimpleperf/scripts/test.py69
-rw-r--r--simpleperf/utils.cpp5
-rw-r--r--toolchain-extras/profile-extras-test.cpp2
-rw-r--r--toolchain-extras/profile-extras.cpp2
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
new file mode 100644
index 00000000..61f5258a
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/aggregatable_perf1.data
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/aggregatable_perf2.data b/simpleperf/scripts/script_testdata/aggregatable_perf2.data
new file mode 100644
index 00000000..7b8224e9
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/aggregatable_perf2.data
Binary files differ
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".