314 lines
15 KiB
Python
314 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (C) 2021 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.
|
|
|
|
import os
|
|
import tempfile
|
|
from typing import List, Set
|
|
|
|
from simpleperf_report_lib import ReportLib
|
|
from . test_utils import TestBase, TestHelper
|
|
|
|
|
|
class TestReportLib(TestBase):
|
|
def setUp(self):
|
|
super(TestReportLib, self).setUp()
|
|
self.report_lib = ReportLib()
|
|
|
|
def tearDown(self):
|
|
self.report_lib.Close()
|
|
super(TestReportLib, self).tearDown()
|
|
|
|
def test_build_id(self):
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data'))
|
|
build_id = self.report_lib.GetBuildIdForPath('/data/t2')
|
|
self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de')
|
|
|
|
def test_symbol(self):
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data'))
|
|
found_func2 = False
|
|
while self.report_lib.GetNextSample():
|
|
symbol = self.report_lib.GetSymbolOfCurrentSample()
|
|
if symbol.symbol_name == 'func2(int, int)':
|
|
found_func2 = True
|
|
self.assertEqual(symbol.symbol_addr, 0x4004ed)
|
|
self.assertEqual(symbol.symbol_len, 0x14)
|
|
self.assertTrue(found_func2)
|
|
|
|
def test_sample(self):
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data'))
|
|
found_sample = False
|
|
while self.report_lib.GetNextSample():
|
|
sample = self.report_lib.GetCurrentSample()
|
|
if sample.ip == 0x4004ff and sample.time == 7637889424953:
|
|
found_sample = True
|
|
self.assertEqual(sample.pid, 15926)
|
|
self.assertEqual(sample.tid, 15926)
|
|
self.assertEqual(sample.thread_comm, 't2')
|
|
self.assertEqual(sample.cpu, 5)
|
|
self.assertEqual(sample.period, 694614)
|
|
event = self.report_lib.GetEventOfCurrentSample()
|
|
self.assertEqual(event.name, 'cpu-cycles')
|
|
callchain = self.report_lib.GetCallChainOfCurrentSample()
|
|
self.assertEqual(callchain.nr, 0)
|
|
self.assertTrue(found_sample)
|
|
|
|
def test_meta_info(self):
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data'))
|
|
meta_info = self.report_lib.MetaInfo()
|
|
self.assertTrue("simpleperf_version" in meta_info)
|
|
self.assertEqual(meta_info["system_wide_collection"], "false")
|
|
self.assertEqual(meta_info["trace_offcpu"], "true")
|
|
self.assertEqual(meta_info["event_type_info"], "cpu-clock,1,0\nsched:sched_switch,2,91")
|
|
self.assertTrue("product_props" in meta_info)
|
|
|
|
def test_event_name_from_meta_info(self):
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data'))
|
|
event_names = set()
|
|
while self.report_lib.GetNextSample():
|
|
event_names.add(self.report_lib.GetEventOfCurrentSample().name)
|
|
self.assertTrue('sched:sched_switch' in event_names)
|
|
self.assertTrue('cpu-cycles' in event_names)
|
|
|
|
def test_record_cmd(self):
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data'))
|
|
self.assertEqual(self.report_lib.GetRecordCmd(),
|
|
'/data/user/0/com.google.samples.apps.sunflower/simpleperf record ' +
|
|
'--app com.google.samples.apps.sunflower --add-meta-info ' +
|
|
'app_type=debuggable --in-app --tracepoint-events ' +
|
|
'/data/local/tmp/tracepoint_events --out-fd 3 --stop-signal-fd 4 -g ' +
|
|
'--size-limit 500k --trace-offcpu -e cpu-clock:u')
|
|
|
|
def test_offcpu(self):
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data'))
|
|
total_period = 0
|
|
sleep_function_period = 0
|
|
sleep_function_name = "__epoll_pwait"
|
|
while self.report_lib.GetNextSample():
|
|
sample = self.report_lib.GetCurrentSample()
|
|
total_period += sample.period
|
|
if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name:
|
|
sleep_function_period += sample.period
|
|
continue
|
|
callchain = self.report_lib.GetCallChainOfCurrentSample()
|
|
for i in range(callchain.nr):
|
|
if callchain.entries[i].symbol.symbol_name == sleep_function_name:
|
|
sleep_function_period += sample.period
|
|
break
|
|
self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-clock:u')
|
|
sleep_percentage = float(sleep_function_period) / total_period
|
|
self.assertGreater(sleep_percentage, 0.30)
|
|
|
|
def test_show_art_frames(self):
|
|
def has_art_frame(report_lib):
|
|
report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data'))
|
|
result = False
|
|
while report_lib.GetNextSample():
|
|
callchain = report_lib.GetCallChainOfCurrentSample()
|
|
for i in range(callchain.nr):
|
|
if callchain.entries[i].symbol.symbol_name == 'artMterpAsmInstructionStart':
|
|
result = True
|
|
break
|
|
report_lib.Close()
|
|
return result
|
|
|
|
report_lib = ReportLib()
|
|
self.assertFalse(has_art_frame(report_lib))
|
|
report_lib = ReportLib()
|
|
report_lib.ShowArtFrames(False)
|
|
self.assertFalse(has_art_frame(report_lib))
|
|
report_lib = ReportLib()
|
|
report_lib.ShowArtFrames(True)
|
|
self.assertTrue(has_art_frame(report_lib))
|
|
|
|
def test_merge_java_methods(self):
|
|
def parse_dso_names(report_lib):
|
|
dso_names = set()
|
|
report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data'))
|
|
while report_lib.GetNextSample():
|
|
dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name)
|
|
callchain = report_lib.GetCallChainOfCurrentSample()
|
|
for i in range(callchain.nr):
|
|
dso_names.add(callchain.entries[i].symbol.dso_name)
|
|
report_lib.Close()
|
|
has_jit_symfiles = any('TemporaryFile-' in name for name in dso_names)
|
|
has_jit_cache = '[JIT cache]' in dso_names
|
|
return has_jit_symfiles, has_jit_cache
|
|
|
|
report_lib = ReportLib()
|
|
self.assertEqual(parse_dso_names(report_lib), (False, True))
|
|
|
|
report_lib = ReportLib()
|
|
report_lib.MergeJavaMethods(True)
|
|
self.assertEqual(parse_dso_names(report_lib), (False, True))
|
|
|
|
report_lib = ReportLib()
|
|
report_lib.MergeJavaMethods(False)
|
|
self.assertEqual(parse_dso_names(report_lib), (True, False))
|
|
|
|
def test_jited_java_methods(self):
|
|
report_lib = ReportLib()
|
|
report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_jit_symbol.data'))
|
|
has_jit_cache = False
|
|
while report_lib.GetNextSample():
|
|
if report_lib.GetSymbolOfCurrentSample().dso_name == '[JIT app cache]':
|
|
has_jit_cache = True
|
|
callchain = report_lib.GetCallChainOfCurrentSample()
|
|
for i in range(callchain.nr):
|
|
if callchain.entries[i].symbol.dso_name == '[JIT app cache]':
|
|
has_jit_cache = True
|
|
report_lib.Close()
|
|
self.assertTrue(has_jit_cache)
|
|
|
|
def test_tracing_data(self):
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data'))
|
|
has_tracing_data = False
|
|
while self.report_lib.GetNextSample():
|
|
event = self.report_lib.GetEventOfCurrentSample()
|
|
tracing_data = self.report_lib.GetTracingDataOfCurrentSample()
|
|
if event.name == 'sched:sched_switch':
|
|
self.assertIsNotNone(tracing_data)
|
|
self.assertIn('prev_pid', tracing_data)
|
|
self.assertIn('next_comm', tracing_data)
|
|
if tracing_data['prev_pid'] == 9896 and tracing_data['next_comm'] == 'swapper/4':
|
|
has_tracing_data = True
|
|
else:
|
|
self.assertIsNone(tracing_data)
|
|
self.assertTrue(has_tracing_data)
|
|
|
|
def test_dynamic_field_in_tracing_data(self):
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path(
|
|
'perf_with_tracepoint_event_dynamic_field.data'))
|
|
has_dynamic_field = False
|
|
while self.report_lib.GetNextSample():
|
|
event = self.report_lib.GetEventOfCurrentSample()
|
|
tracing_data = self.report_lib.GetTracingDataOfCurrentSample()
|
|
if event.name == 'kprobes:myopen':
|
|
self.assertIsNotNone(tracing_data)
|
|
self.assertIn('name', tracing_data)
|
|
if tracing_data['name'] == '/sys/kernel/debug/tracing/events/kprobes/myopen/format':
|
|
has_dynamic_field = True
|
|
else:
|
|
self.assertIsNone(tracing_data)
|
|
self.assertTrue(has_dynamic_field)
|
|
|
|
def test_add_proguard_mapping_file(self):
|
|
with self.assertRaises(ValueError):
|
|
self.report_lib.AddProguardMappingFile('non_exist_file')
|
|
proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt')
|
|
self.report_lib.AddProguardMappingFile(proguard_mapping_file)
|
|
|
|
def test_set_trace_offcpu_mode(self):
|
|
# GetSupportedTraceOffCpuModes() before SetRecordFile() triggers RuntimeError.
|
|
with self.assertRaises(RuntimeError):
|
|
self.report_lib.GetSupportedTraceOffCpuModes()
|
|
# SetTraceOffCpuModes() before SetRecordFile() triggers RuntimeError.
|
|
with self.assertRaises(RuntimeError):
|
|
self.report_lib.SetTraceOffCpuMode('on-cpu')
|
|
|
|
mode_dict = {
|
|
'on-cpu': {
|
|
'cpu-clock:u': (208, 52000000),
|
|
'sched:sched_switch': (0, 0),
|
|
},
|
|
'off-cpu': {
|
|
'cpu-clock:u': (0, 0),
|
|
'sched:sched_switch': (91, 344124304),
|
|
},
|
|
'on-off-cpu': {
|
|
'cpu-clock:u': (208, 52000000),
|
|
'sched:sched_switch': (91, 344124304),
|
|
},
|
|
'mixed-on-off-cpu': {
|
|
'cpu-clock:u': (299, 396124304),
|
|
'sched:sched_switch': (0, 0),
|
|
},
|
|
}
|
|
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data'))
|
|
self.assertEqual(set(self.report_lib.GetSupportedTraceOffCpuModes()), set(mode_dict.keys()))
|
|
for mode, expected_values in mode_dict.items():
|
|
self.report_lib.Close()
|
|
self.report_lib = ReportLib()
|
|
self.report_lib.SetRecordFile(
|
|
TestHelper.testdata_path('perf_with_trace_offcpu_v2.data'))
|
|
self.report_lib.SetTraceOffCpuMode(mode)
|
|
|
|
cpu_clock_period = 0
|
|
cpu_clock_samples = 0
|
|
sched_switch_period = 0
|
|
sched_switch_samples = 0
|
|
while self.report_lib.GetNextSample():
|
|
sample = self.report_lib.GetCurrentSample()
|
|
event = self.report_lib.GetEventOfCurrentSample()
|
|
if event.name == 'cpu-clock:u':
|
|
cpu_clock_period += sample.period
|
|
cpu_clock_samples += 1
|
|
else:
|
|
self.assertEqual(event.name, 'sched:sched_switch')
|
|
sched_switch_period += sample.period
|
|
sched_switch_samples += 1
|
|
self.assertEqual(cpu_clock_samples, expected_values['cpu-clock:u'][0])
|
|
self.assertEqual(cpu_clock_period, expected_values['cpu-clock:u'][1])
|
|
self.assertEqual(sched_switch_samples, expected_values['sched:sched_switch'][0])
|
|
self.assertEqual(sched_switch_period, expected_values['sched:sched_switch'][1])
|
|
|
|
# Check trace-offcpu modes on a profile not recorded with --trace-offcpu.
|
|
self.report_lib.Close()
|
|
self.report_lib = ReportLib()
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf.data'))
|
|
self.assertEqual(self.report_lib.GetSupportedTraceOffCpuModes(), [])
|
|
with self.assertRaises(RuntimeError):
|
|
self.report_lib.SetTraceOffCpuMode('on-cpu')
|
|
|
|
def test_set_sample_filter(self):
|
|
""" Test using ReportLib.SetSampleFilter(). """
|
|
def get_threads_for_filter(filters: List[str]) -> Set[int]:
|
|
self.report_lib.Close()
|
|
self.report_lib = ReportLib()
|
|
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data'))
|
|
self.report_lib.SetSampleFilter(filters)
|
|
threads = set()
|
|
while self.report_lib.GetNextSample():
|
|
sample = self.report_lib.GetCurrentSample()
|
|
threads.add(sample.tid)
|
|
return threads
|
|
|
|
self.assertNotIn(31850, get_threads_for_filter(['--exclude-pid', '31850']))
|
|
self.assertIn(31850, get_threads_for_filter(['--include-pid', '31850']))
|
|
self.assertNotIn(31881, get_threads_for_filter(['--exclude-tid', '31881']))
|
|
self.assertIn(31881, get_threads_for_filter(['--include-tid', '31881']))
|
|
self.assertNotIn(31881, get_threads_for_filter(
|
|
['--exclude-process-name', 'com.example.android.displayingbitmaps']))
|
|
self.assertIn(31881, get_threads_for_filter(
|
|
['--include-process-name', 'com.example.android.displayingbitmaps']))
|
|
self.assertNotIn(31850, get_threads_for_filter(
|
|
['--exclude-thread-name', 'com.example.android.displayingbitmaps']))
|
|
self.assertIn(31850, get_threads_for_filter(
|
|
['--include-thread-name', 'com.example.android.displayingbitmaps']))
|
|
|
|
# Check that thread name can have space.
|
|
self.assertNotIn(31856, get_threads_for_filter(
|
|
['--exclude-thread-name', 'Jit thread pool']))
|
|
self.assertIn(31856, get_threads_for_filter(['--include-thread-name', 'Jit thread pool']))
|
|
|
|
with tempfile.NamedTemporaryFile('w', delete=False) as filter_file:
|
|
filter_file.write('GLOBAL_BEGIN 684943449406175\nGLOBAL_END 684943449406176')
|
|
filter_file.flush()
|
|
threads = get_threads_for_filter(['--filter-file', filter_file.name])
|
|
self.assertIn(31881, threads)
|
|
self.assertNotIn(31850, threads)
|
|
os.unlink(filter_file.name)
|