1#!/usr/bin/env python3 2# 3# Copyright (C) 2021 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import os 18import tempfile 19from typing import List, Set 20 21from simpleperf_report_lib import ReportLib 22from . test_utils import TestBase, TestHelper 23 24 25class TestReportLib(TestBase): 26 def setUp(self): 27 super(TestReportLib, self).setUp() 28 self.report_lib = ReportLib() 29 30 def tearDown(self): 31 self.report_lib.Close() 32 super(TestReportLib, self).tearDown() 33 34 def test_build_id(self): 35 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data')) 36 build_id = self.report_lib.GetBuildIdForPath('/data/t2') 37 self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') 38 39 def test_symbol(self): 40 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data')) 41 found_func2 = False 42 while self.report_lib.GetNextSample(): 43 symbol = self.report_lib.GetSymbolOfCurrentSample() 44 if symbol.symbol_name == 'func2(int, int)': 45 found_func2 = True 46 self.assertEqual(symbol.symbol_addr, 0x4004ed) 47 self.assertEqual(symbol.symbol_len, 0x14) 48 self.assertTrue(found_func2) 49 50 def test_sample(self): 51 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data')) 52 found_sample = False 53 while self.report_lib.GetNextSample(): 54 sample = self.report_lib.GetCurrentSample() 55 if sample.ip == 0x4004ff and sample.time == 7637889424953: 56 found_sample = True 57 self.assertEqual(sample.pid, 15926) 58 self.assertEqual(sample.tid, 15926) 59 self.assertEqual(sample.thread_comm, 't2') 60 self.assertEqual(sample.cpu, 5) 61 self.assertEqual(sample.period, 694614) 62 event = self.report_lib.GetEventOfCurrentSample() 63 self.assertEqual(event.name, 'cpu-cycles') 64 callchain = self.report_lib.GetCallChainOfCurrentSample() 65 self.assertEqual(callchain.nr, 0) 66 self.assertTrue(found_sample) 67 68 def test_meta_info(self): 69 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 70 meta_info = self.report_lib.MetaInfo() 71 self.assertTrue("simpleperf_version" in meta_info) 72 self.assertEqual(meta_info["system_wide_collection"], "false") 73 self.assertEqual(meta_info["trace_offcpu"], "true") 74 self.assertEqual(meta_info["event_type_info"], "cpu-clock,1,0\nsched:sched_switch,2,91") 75 self.assertTrue("product_props" in meta_info) 76 77 def test_event_name_from_meta_info(self): 78 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data')) 79 event_names = set() 80 while self.report_lib.GetNextSample(): 81 event_names.add(self.report_lib.GetEventOfCurrentSample().name) 82 self.assertTrue('sched:sched_switch' in event_names) 83 self.assertTrue('cpu-cycles' in event_names) 84 85 def test_record_cmd(self): 86 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 87 self.assertEqual(self.report_lib.GetRecordCmd(), 88 '/data/user/0/com.google.samples.apps.sunflower/simpleperf record ' + 89 '--app com.google.samples.apps.sunflower --add-meta-info ' + 90 'app_type=debuggable --in-app --tracepoint-events ' + 91 '/data/local/tmp/tracepoint_events --out-fd 3 --stop-signal-fd 4 -g ' + 92 '--size-limit 500k --trace-offcpu -e cpu-clock:u') 93 94 def test_offcpu(self): 95 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 96 total_period = 0 97 sleep_function_period = 0 98 sleep_function_name = "__epoll_pwait" 99 while self.report_lib.GetNextSample(): 100 sample = self.report_lib.GetCurrentSample() 101 total_period += sample.period 102 if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name: 103 sleep_function_period += sample.period 104 continue 105 callchain = self.report_lib.GetCallChainOfCurrentSample() 106 for i in range(callchain.nr): 107 if callchain.entries[i].symbol.symbol_name == sleep_function_name: 108 sleep_function_period += sample.period 109 break 110 self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-clock:u') 111 sleep_percentage = float(sleep_function_period) / total_period 112 self.assertGreater(sleep_percentage, 0.30) 113 114 def test_show_art_frames(self): 115 def has_art_frame(report_lib): 116 report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data')) 117 result = False 118 while report_lib.GetNextSample(): 119 callchain = report_lib.GetCallChainOfCurrentSample() 120 for i in range(callchain.nr): 121 if callchain.entries[i].symbol.symbol_name == 'artMterpAsmInstructionStart': 122 result = True 123 break 124 report_lib.Close() 125 return result 126 127 report_lib = ReportLib() 128 self.assertFalse(has_art_frame(report_lib)) 129 report_lib = ReportLib() 130 report_lib.ShowArtFrames(False) 131 self.assertFalse(has_art_frame(report_lib)) 132 report_lib = ReportLib() 133 report_lib.ShowArtFrames(True) 134 self.assertTrue(has_art_frame(report_lib)) 135 136 def test_merge_java_methods(self): 137 def parse_dso_names(report_lib): 138 dso_names = set() 139 report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data')) 140 while report_lib.GetNextSample(): 141 dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name) 142 callchain = report_lib.GetCallChainOfCurrentSample() 143 for i in range(callchain.nr): 144 dso_names.add(callchain.entries[i].symbol.dso_name) 145 report_lib.Close() 146 has_jit_symfiles = any('TemporaryFile-' in name for name in dso_names) 147 has_jit_cache = '[JIT cache]' in dso_names 148 return has_jit_symfiles, has_jit_cache 149 150 report_lib = ReportLib() 151 self.assertEqual(parse_dso_names(report_lib), (False, True)) 152 153 report_lib = ReportLib() 154 report_lib.MergeJavaMethods(True) 155 self.assertEqual(parse_dso_names(report_lib), (False, True)) 156 157 report_lib = ReportLib() 158 report_lib.MergeJavaMethods(False) 159 self.assertEqual(parse_dso_names(report_lib), (True, False)) 160 161 def test_jited_java_methods(self): 162 report_lib = ReportLib() 163 report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_jit_symbol.data')) 164 has_jit_cache = False 165 while report_lib.GetNextSample(): 166 if report_lib.GetSymbolOfCurrentSample().dso_name == '[JIT app cache]': 167 has_jit_cache = True 168 callchain = report_lib.GetCallChainOfCurrentSample() 169 for i in range(callchain.nr): 170 if callchain.entries[i].symbol.dso_name == '[JIT app cache]': 171 has_jit_cache = True 172 report_lib.Close() 173 self.assertTrue(has_jit_cache) 174 175 def test_tracing_data(self): 176 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data')) 177 has_tracing_data = False 178 while self.report_lib.GetNextSample(): 179 event = self.report_lib.GetEventOfCurrentSample() 180 tracing_data = self.report_lib.GetTracingDataOfCurrentSample() 181 if event.name == 'sched:sched_switch': 182 self.assertIsNotNone(tracing_data) 183 self.assertIn('prev_pid', tracing_data) 184 self.assertIn('next_comm', tracing_data) 185 if tracing_data['prev_pid'] == 9896 and tracing_data['next_comm'] == 'swapper/4': 186 has_tracing_data = True 187 else: 188 self.assertIsNone(tracing_data) 189 self.assertTrue(has_tracing_data) 190 191 def test_dynamic_field_in_tracing_data(self): 192 self.report_lib.SetRecordFile(TestHelper.testdata_path( 193 'perf_with_tracepoint_event_dynamic_field.data')) 194 has_dynamic_field = False 195 while self.report_lib.GetNextSample(): 196 event = self.report_lib.GetEventOfCurrentSample() 197 tracing_data = self.report_lib.GetTracingDataOfCurrentSample() 198 if event.name == 'kprobes:myopen': 199 self.assertIsNotNone(tracing_data) 200 self.assertIn('name', tracing_data) 201 if tracing_data['name'] == '/sys/kernel/debug/tracing/events/kprobes/myopen/format': 202 has_dynamic_field = True 203 else: 204 self.assertIsNone(tracing_data) 205 self.assertTrue(has_dynamic_field) 206 207 def test_add_proguard_mapping_file(self): 208 with self.assertRaises(ValueError): 209 self.report_lib.AddProguardMappingFile('non_exist_file') 210 proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt') 211 self.report_lib.AddProguardMappingFile(proguard_mapping_file) 212 213 def test_set_trace_offcpu_mode(self): 214 # GetSupportedTraceOffCpuModes() before SetRecordFile() triggers RuntimeError. 215 with self.assertRaises(RuntimeError): 216 self.report_lib.GetSupportedTraceOffCpuModes() 217 # SetTraceOffCpuModes() before SetRecordFile() triggers RuntimeError. 218 with self.assertRaises(RuntimeError): 219 self.report_lib.SetTraceOffCpuMode('on-cpu') 220 221 mode_dict = { 222 'on-cpu': { 223 'cpu-clock:u': (208, 52000000), 224 'sched:sched_switch': (0, 0), 225 }, 226 'off-cpu': { 227 'cpu-clock:u': (0, 0), 228 'sched:sched_switch': (91, 344124304), 229 }, 230 'on-off-cpu': { 231 'cpu-clock:u': (208, 52000000), 232 'sched:sched_switch': (91, 344124304), 233 }, 234 'mixed-on-off-cpu': { 235 'cpu-clock:u': (299, 396124304), 236 'sched:sched_switch': (0, 0), 237 }, 238 } 239 240 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 241 self.assertEqual(set(self.report_lib.GetSupportedTraceOffCpuModes()), set(mode_dict.keys())) 242 for mode, expected_values in mode_dict.items(): 243 self.report_lib.Close() 244 self.report_lib = ReportLib() 245 self.report_lib.SetRecordFile( 246 TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 247 self.report_lib.SetTraceOffCpuMode(mode) 248 249 cpu_clock_period = 0 250 cpu_clock_samples = 0 251 sched_switch_period = 0 252 sched_switch_samples = 0 253 while self.report_lib.GetNextSample(): 254 sample = self.report_lib.GetCurrentSample() 255 event = self.report_lib.GetEventOfCurrentSample() 256 if event.name == 'cpu-clock:u': 257 cpu_clock_period += sample.period 258 cpu_clock_samples += 1 259 else: 260 self.assertEqual(event.name, 'sched:sched_switch') 261 sched_switch_period += sample.period 262 sched_switch_samples += 1 263 self.assertEqual(cpu_clock_samples, expected_values['cpu-clock:u'][0]) 264 self.assertEqual(cpu_clock_period, expected_values['cpu-clock:u'][1]) 265 self.assertEqual(sched_switch_samples, expected_values['sched:sched_switch'][0]) 266 self.assertEqual(sched_switch_period, expected_values['sched:sched_switch'][1]) 267 268 # Check trace-offcpu modes on a profile not recorded with --trace-offcpu. 269 self.report_lib.Close() 270 self.report_lib = ReportLib() 271 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf.data')) 272 self.assertEqual(self.report_lib.GetSupportedTraceOffCpuModes(), []) 273 with self.assertRaises(RuntimeError): 274 self.report_lib.SetTraceOffCpuMode('on-cpu') 275 276 def test_set_sample_filter(self): 277 """ Test using ReportLib.SetSampleFilter(). """ 278 def get_threads_for_filter(filters: List[str]) -> Set[int]: 279 self.report_lib.Close() 280 self.report_lib = ReportLib() 281 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data')) 282 self.report_lib.SetSampleFilter(filters) 283 threads = set() 284 while self.report_lib.GetNextSample(): 285 sample = self.report_lib.GetCurrentSample() 286 threads.add(sample.tid) 287 return threads 288 289 self.assertNotIn(31850, get_threads_for_filter(['--exclude-pid', '31850'])) 290 self.assertIn(31850, get_threads_for_filter(['--include-pid', '31850'])) 291 self.assertNotIn(31881, get_threads_for_filter(['--exclude-tid', '31881'])) 292 self.assertIn(31881, get_threads_for_filter(['--include-tid', '31881'])) 293 self.assertNotIn(31881, get_threads_for_filter( 294 ['--exclude-process-name', 'com.example.android.displayingbitmaps'])) 295 self.assertIn(31881, get_threads_for_filter( 296 ['--include-process-name', 'com.example.android.displayingbitmaps'])) 297 self.assertNotIn(31850, get_threads_for_filter( 298 ['--exclude-thread-name', 'com.example.android.displayingbitmaps'])) 299 self.assertIn(31850, get_threads_for_filter( 300 ['--include-thread-name', 'com.example.android.displayingbitmaps'])) 301 302 # Check that thread name can have space. 303 self.assertNotIn(31856, get_threads_for_filter( 304 ['--exclude-thread-name', 'Jit thread pool'])) 305 self.assertIn(31856, get_threads_for_filter(['--include-thread-name', 'Jit thread pool'])) 306 307 with tempfile.NamedTemporaryFile('w', delete=False) as filter_file: 308 filter_file.write('GLOBAL_BEGIN 684943449406175\nGLOBAL_END 684943449406176') 309 filter_file.flush() 310 threads = get_threads_for_filter(['--filter-file', filter_file.name]) 311 self.assertIn(31881, threads) 312 self.assertNotIn(31850, threads) 313 os.unlink(filter_file.name) 314