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 18from pathlib import Path 19import shutil 20import subprocess 21import tempfile 22from typing import Dict, List, Optional, Set 23 24from simpleperf_report_lib import ReportLib, ProtoFileReportLib 25from simpleperf_utils import get_host_binary_path, ReadElf 26from . test_utils import TestBase, TestHelper 27 28 29class TestReportLib(TestBase): 30 def setUp(self): 31 super(TestReportLib, self).setUp() 32 self.report_lib = ReportLib() 33 34 def tearDown(self): 35 self.report_lib.Close() 36 super(TestReportLib, self).tearDown() 37 38 def test_build_id(self): 39 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data')) 40 build_id = self.report_lib.GetBuildIdForPath('/data/t2') 41 self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') 42 43 def test_symbol(self): 44 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data')) 45 found_func2 = False 46 while self.report_lib.GetNextSample(): 47 symbol = self.report_lib.GetSymbolOfCurrentSample() 48 if symbol.symbol_name == 'func2(int, int)': 49 found_func2 = True 50 self.assertEqual(symbol.symbol_addr, 0x4004ed) 51 self.assertEqual(symbol.symbol_len, 0x14) 52 self.assertTrue(found_func2) 53 54 def test_sample(self): 55 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data')) 56 found_sample = False 57 while self.report_lib.GetNextSample(): 58 sample = self.report_lib.GetCurrentSample() 59 if sample.ip == 0x4004ff and sample.time == 7637889424953: 60 found_sample = True 61 self.assertEqual(sample.pid, 15926) 62 self.assertEqual(sample.tid, 15926) 63 self.assertEqual(sample.thread_comm, 't2') 64 self.assertEqual(sample.cpu, 5) 65 self.assertEqual(sample.period, 694614) 66 event = self.report_lib.GetEventOfCurrentSample() 67 self.assertEqual(event.name, 'cpu-cycles') 68 callchain = self.report_lib.GetCallChainOfCurrentSample() 69 self.assertEqual(callchain.nr, 0) 70 self.assertTrue(found_sample) 71 72 def test_meta_info(self): 73 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 74 meta_info = self.report_lib.MetaInfo() 75 self.assertTrue("simpleperf_version" in meta_info) 76 self.assertEqual(meta_info["system_wide_collection"], "false") 77 self.assertEqual(meta_info["trace_offcpu"], "true") 78 self.assertEqual(meta_info["event_type_info"], "cpu-clock,1,0\nsched:sched_switch,2,91") 79 self.assertTrue("product_props" in meta_info) 80 81 def test_event_name_from_meta_info(self): 82 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data')) 83 event_names = set() 84 while self.report_lib.GetNextSample(): 85 event_names.add(self.report_lib.GetEventOfCurrentSample().name) 86 self.assertTrue('sched:sched_switch' in event_names) 87 self.assertTrue('cpu-cycles' in event_names) 88 89 def test_record_cmd(self): 90 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 91 self.assertEqual(self.report_lib.GetRecordCmd(), 92 '/data/user/0/com.google.samples.apps.sunflower/simpleperf record ' + 93 '--app com.google.samples.apps.sunflower --add-meta-info ' + 94 'app_type=debuggable --in-app --tracepoint-events ' + 95 '/data/local/tmp/tracepoint_events --out-fd 3 --stop-signal-fd 4 -g ' + 96 '--size-limit 500k --trace-offcpu -e cpu-clock:u') 97 98 def test_offcpu(self): 99 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 100 total_period = 0 101 sleep_function_period = 0 102 sleep_function_name = "__epoll_pwait" 103 while self.report_lib.GetNextSample(): 104 sample = self.report_lib.GetCurrentSample() 105 total_period += sample.period 106 if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name: 107 sleep_function_period += sample.period 108 continue 109 callchain = self.report_lib.GetCallChainOfCurrentSample() 110 for i in range(callchain.nr): 111 if callchain.entries[i].symbol.symbol_name == sleep_function_name: 112 sleep_function_period += sample.period 113 break 114 self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-clock:u') 115 sleep_percentage = float(sleep_function_period) / total_period 116 self.assertGreater(sleep_percentage, 0.30) 117 118 def test_show_art_frames(self): 119 def has_art_frame(report_lib): 120 report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data')) 121 result = False 122 while report_lib.GetNextSample(): 123 callchain = report_lib.GetCallChainOfCurrentSample() 124 for i in range(callchain.nr): 125 if callchain.entries[i].symbol.symbol_name == 'artMterpAsmInstructionStart': 126 result = True 127 break 128 report_lib.Close() 129 return result 130 131 report_lib = ReportLib() 132 self.assertFalse(has_art_frame(report_lib)) 133 report_lib = ReportLib() 134 report_lib.ShowArtFrames(False) 135 self.assertFalse(has_art_frame(report_lib)) 136 report_lib = ReportLib() 137 report_lib.ShowArtFrames(True) 138 self.assertTrue(has_art_frame(report_lib)) 139 140 def test_remove_method(self): 141 def get_methods(report_lib) -> Set[str]: 142 methods = set() 143 report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data')) 144 while True: 145 sample = report_lib.GetNextSample() 146 if not sample: 147 break 148 methods.add(report_lib.GetSymbolOfCurrentSample().symbol_name) 149 callchain = report_lib.GetCallChainOfCurrentSample() 150 for i in range(callchain.nr): 151 methods.add(callchain.entries[i].symbol.symbol_name) 152 report_lib.Close() 153 return methods 154 155 report_lib = ReportLib() 156 report_lib.RemoveMethod('android.view') 157 methods = get_methods(report_lib) 158 self.assertFalse(any('android.view' in method for method in methods)) 159 self.assertTrue(any('android.widget' in method for method in methods)) 160 161 def test_merge_java_methods(self): 162 def parse_dso_names(report_lib): 163 dso_names = set() 164 report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data')) 165 while report_lib.GetNextSample(): 166 dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name) 167 callchain = report_lib.GetCallChainOfCurrentSample() 168 for i in range(callchain.nr): 169 dso_names.add(callchain.entries[i].symbol.dso_name) 170 report_lib.Close() 171 has_jit_symfiles = any('TemporaryFile-' in name for name in dso_names) 172 has_jit_cache = '[JIT cache]' in dso_names 173 return has_jit_symfiles, has_jit_cache 174 175 report_lib = ReportLib() 176 self.assertEqual(parse_dso_names(report_lib), (False, True)) 177 178 report_lib = ReportLib() 179 report_lib.MergeJavaMethods(True) 180 self.assertEqual(parse_dso_names(report_lib), (False, True)) 181 182 report_lib = ReportLib() 183 report_lib.MergeJavaMethods(False) 184 self.assertEqual(parse_dso_names(report_lib), (True, False)) 185 186 def test_jited_java_methods(self): 187 report_lib = ReportLib() 188 report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_jit_symbol.data')) 189 has_jit_cache = False 190 while report_lib.GetNextSample(): 191 if report_lib.GetSymbolOfCurrentSample().dso_name == '[JIT app cache]': 192 has_jit_cache = True 193 callchain = report_lib.GetCallChainOfCurrentSample() 194 for i in range(callchain.nr): 195 if callchain.entries[i].symbol.dso_name == '[JIT app cache]': 196 has_jit_cache = True 197 report_lib.Close() 198 self.assertTrue(has_jit_cache) 199 200 def test_tracing_data(self): 201 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data')) 202 has_tracing_data = False 203 while self.report_lib.GetNextSample(): 204 event = self.report_lib.GetEventOfCurrentSample() 205 tracing_data = self.report_lib.GetTracingDataOfCurrentSample() 206 if event.name == 'sched:sched_switch': 207 self.assertIsNotNone(tracing_data) 208 self.assertIn('prev_pid', tracing_data) 209 self.assertIn('next_comm', tracing_data) 210 if tracing_data['prev_pid'] == 9896 and tracing_data['next_comm'] == 'swapper/4': 211 has_tracing_data = True 212 else: 213 self.assertIsNone(tracing_data) 214 self.assertTrue(has_tracing_data) 215 216 def test_dynamic_field_in_tracing_data(self): 217 self.report_lib.SetRecordFile(TestHelper.testdata_path( 218 'perf_with_tracepoint_event_dynamic_field.data')) 219 has_dynamic_field = False 220 while self.report_lib.GetNextSample(): 221 event = self.report_lib.GetEventOfCurrentSample() 222 tracing_data = self.report_lib.GetTracingDataOfCurrentSample() 223 if event.name == 'kprobes:myopen': 224 self.assertIsNotNone(tracing_data) 225 self.assertIn('name', tracing_data) 226 if tracing_data['name'] == '/sys/kernel/debug/tracing/events/kprobes/myopen/format': 227 has_dynamic_field = True 228 else: 229 self.assertIsNone(tracing_data) 230 self.assertTrue(has_dynamic_field) 231 232 def test_add_proguard_mapping_file(self): 233 with self.assertRaises(ValueError): 234 self.report_lib.AddProguardMappingFile('non_exist_file') 235 proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt') 236 self.report_lib.AddProguardMappingFile(proguard_mapping_file) 237 238 def test_set_trace_offcpu_mode(self): 239 # GetSupportedTraceOffCpuModes() before SetRecordFile() triggers RuntimeError. 240 with self.assertRaises(RuntimeError): 241 self.report_lib.GetSupportedTraceOffCpuModes() 242 # SetTraceOffCpuModes() before SetRecordFile() triggers RuntimeError. 243 with self.assertRaises(RuntimeError): 244 self.report_lib.SetTraceOffCpuMode('on-cpu') 245 246 mode_dict = { 247 'on-cpu': { 248 'cpu-clock:u': (208, 52000000), 249 'sched:sched_switch': (0, 0), 250 }, 251 'off-cpu': { 252 'cpu-clock:u': (0, 0), 253 'sched:sched_switch': (91, 344124304), 254 }, 255 'on-off-cpu': { 256 'cpu-clock:u': (208, 52000000), 257 'sched:sched_switch': (91, 344124304), 258 }, 259 'mixed-on-off-cpu': { 260 'cpu-clock:u': (299, 396124304), 261 'sched:sched_switch': (0, 0), 262 }, 263 } 264 265 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 266 self.assertEqual(set(self.report_lib.GetSupportedTraceOffCpuModes()), set(mode_dict.keys())) 267 for mode, expected_values in mode_dict.items(): 268 self.report_lib.Close() 269 self.report_lib = ReportLib() 270 self.report_lib.SetRecordFile( 271 TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 272 self.report_lib.SetTraceOffCpuMode(mode) 273 274 cpu_clock_period = 0 275 cpu_clock_samples = 0 276 sched_switch_period = 0 277 sched_switch_samples = 0 278 while self.report_lib.GetNextSample(): 279 sample = self.report_lib.GetCurrentSample() 280 event = self.report_lib.GetEventOfCurrentSample() 281 if event.name == 'cpu-clock:u': 282 cpu_clock_period += sample.period 283 cpu_clock_samples += 1 284 else: 285 self.assertEqual(event.name, 'sched:sched_switch') 286 sched_switch_period += sample.period 287 sched_switch_samples += 1 288 self.assertEqual(cpu_clock_samples, expected_values['cpu-clock:u'][0]) 289 self.assertEqual(cpu_clock_period, expected_values['cpu-clock:u'][1]) 290 self.assertEqual(sched_switch_samples, expected_values['sched:sched_switch'][0]) 291 self.assertEqual(sched_switch_period, expected_values['sched:sched_switch'][1]) 292 293 # Check trace-offcpu modes on a profile not recorded with --trace-offcpu. 294 self.report_lib.Close() 295 self.report_lib = ReportLib() 296 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf.data')) 297 self.assertEqual(self.report_lib.GetSupportedTraceOffCpuModes(), []) 298 with self.assertRaises(RuntimeError): 299 self.report_lib.SetTraceOffCpuMode('on-cpu') 300 301 def test_set_sample_filter(self): 302 """ Test using ReportLib.SetSampleFilter(). """ 303 def get_threads_for_filter(filters: List[str]) -> Set[int]: 304 self.report_lib.Close() 305 self.report_lib = ReportLib() 306 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data')) 307 self.report_lib.SetSampleFilter(filters) 308 threads = set() 309 while self.report_lib.GetNextSample(): 310 sample = self.report_lib.GetCurrentSample() 311 threads.add(sample.tid) 312 return threads 313 314 self.assertNotIn(31850, get_threads_for_filter(['--exclude-pid', '31850'])) 315 self.assertIn(31850, get_threads_for_filter(['--include-pid', '31850'])) 316 self.assertNotIn(31881, get_threads_for_filter(['--exclude-tid', '31881'])) 317 self.assertIn(31881, get_threads_for_filter(['--include-tid', '31881'])) 318 self.assertNotIn(31881, get_threads_for_filter( 319 ['--exclude-process-name', 'com.example.android.displayingbitmaps'])) 320 self.assertIn(31881, get_threads_for_filter( 321 ['--include-process-name', 'com.example.android.displayingbitmaps'])) 322 self.assertNotIn(31850, get_threads_for_filter( 323 ['--exclude-thread-name', 'com.example.android.displayingbitmaps'])) 324 self.assertIn(31850, get_threads_for_filter( 325 ['--include-thread-name', 'com.example.android.displayingbitmaps'])) 326 327 # Check that thread name can have space. 328 self.assertNotIn(31856, get_threads_for_filter( 329 ['--exclude-thread-name', 'Jit thread pool'])) 330 self.assertIn(31856, get_threads_for_filter(['--include-thread-name', 'Jit thread pool'])) 331 332 with tempfile.NamedTemporaryFile('w', delete=False) as filter_file: 333 filter_file.write('GLOBAL_BEGIN 684943449406175\nGLOBAL_END 684943449406176') 334 filter_file.flush() 335 threads = get_threads_for_filter(['--filter-file', filter_file.name]) 336 self.assertIn(31881, threads) 337 self.assertNotIn(31850, threads) 338 os.unlink(filter_file.name) 339 340 def test_set_sample_filter_for_cpu(self): 341 """ Test --cpu in ReportLib.SetSampleFilter(). """ 342 def get_cpus_for_filter(filters: List[str]) -> Set[int]: 343 self.report_lib.Close() 344 self.report_lib = ReportLib() 345 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data')) 346 self.report_lib.SetSampleFilter(filters) 347 cpus = set() 348 while self.report_lib.GetNextSample(): 349 sample = self.report_lib.GetCurrentSample() 350 cpus.add(sample.cpu) 351 return cpus 352 353 cpus = get_cpus_for_filter(['--cpu', '0,1-2']) 354 self.assertIn(0, cpus) 355 self.assertIn(1, cpus) 356 self.assertIn(2, cpus) 357 self.assertNotIn(3, cpus) 358 359 def test_aggregate_threads(self): 360 """ Test using ReportLib.AggregateThreads(). """ 361 def get_thread_names(aggregate_regex_list: Optional[List[str]]) -> Dict[str, int]: 362 self.report_lib.Close() 363 self.report_lib = ReportLib() 364 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data')) 365 if aggregate_regex_list: 366 self.report_lib.AggregateThreads(aggregate_regex_list) 367 thread_names = {} 368 while self.report_lib.GetNextSample(): 369 sample = self.report_lib.GetCurrentSample() 370 thread_names[sample.thread_comm] = thread_names.get(sample.thread_comm, 0) + 1 371 return thread_names 372 thread_names = get_thread_names(None) 373 self.assertEqual(thread_names['AsyncTask #3'], 6) 374 self.assertEqual(thread_names['AsyncTask #4'], 13) 375 thread_names = get_thread_names(['AsyncTask.*']) 376 self.assertEqual(thread_names['AsyncTask.*'], 19) 377 self.assertNotIn('AsyncTask #3', thread_names) 378 self.assertNotIn('AsyncTask #4', thread_names) 379 380 def test_use_vmlinux(self): 381 """ Test if we can use vmlinux in symfs_dir. """ 382 record_file = TestHelper.testdata_path('perf_test_vmlinux.data') 383 # Create a symfs_dir. 384 symfs_dir = Path('symfs_dir') 385 symfs_dir.mkdir() 386 shutil.copy(TestHelper.testdata_path('vmlinux'), symfs_dir) 387 kernel_build_id = ReadElf(TestHelper.ndk_path).get_build_id(symfs_dir / 'vmlinux') 388 (symfs_dir / 'build_id_list').write_text('%s=vmlinux' % kernel_build_id) 389 390 # Check if vmlinux in symfs_dir is used, when we set record file before setting symfs_dir. 391 self.report_lib.SetRecordFile(record_file) 392 self.report_lib.SetSymfs(str(symfs_dir)) 393 sample = self.report_lib.GetNextSample() 394 self.assertIsNotNone(sample) 395 symbol = self.report_lib.GetSymbolOfCurrentSample() 396 self.assertEqual(symbol.dso_name, "[kernel.kallsyms]") 397 # vaddr_in_file and symbol_addr are adjusted after using vmlinux. 398 self.assertEqual(symbol.vaddr_in_file, 0xffffffc008fb3e28) 399 self.assertEqual(symbol.symbol_name, "_raw_spin_unlock_irq") 400 self.assertEqual(symbol.symbol_addr, 0xffffffc008fb3e0c) 401 self.assertEqual(symbol.symbol_len, 0x4c) 402 403 def test_get_process_name(self): 404 self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data')) 405 expected_process_name = 'com.example.android.displayingbitmaps' 406 while self.report_lib.GetNextSample(): 407 process_name = self.report_lib.GetProcessNameOfCurrentSample() 408 self.assertEqual(process_name, expected_process_name) 409 410 411class TestProtoFileReportLib(TestBase): 412 def test_smoke(self): 413 report_lib = ProtoFileReportLib() 414 report_lib.SetRecordFile(TestHelper.testdata_path('display_bitmaps.proto_data')) 415 sample_count = 0 416 while True: 417 sample = report_lib.GetNextSample() 418 if sample is None: 419 report_lib.Close() 420 break 421 sample_count += 1 422 event = report_lib.GetEventOfCurrentSample() 423 self.assertEqual(event.name, 'cpu-clock') 424 report_lib.GetSymbolOfCurrentSample() 425 report_lib.GetCallChainOfCurrentSample() 426 self.assertEqual(sample_count, 525) 427 428 def convert_perf_data_to_proto_file(self, perf_data_path: str) -> str: 429 simpleperf_path = get_host_binary_path('simpleperf') 430 proto_file_path = 'perf.trace' 431 subprocess.check_call([simpleperf_path, 'report-sample', '--show-callchain', '--protobuf', 432 '--remove-gaps', '0', '-i', perf_data_path, '-o', proto_file_path]) 433 return proto_file_path 434 435 def test_set_trace_offcpu_mode(self): 436 report_lib = ProtoFileReportLib() 437 # GetSupportedTraceOffCpuModes() before SetRecordFile() triggers RuntimeError. 438 with self.assertRaises(RuntimeError): 439 report_lib.GetSupportedTraceOffCpuModes() 440 441 mode_dict = { 442 'on-cpu': { 443 'cpu-clock:u': (208, 52000000), 444 'sched:sched_switch': (0, 0), 445 }, 446 'off-cpu': { 447 'cpu-clock:u': (0, 0), 448 'sched:sched_switch': (91, 344124304), 449 }, 450 'on-off-cpu': { 451 'cpu-clock:u': (208, 52000000), 452 'sched:sched_switch': (91, 344124304), 453 }, 454 'mixed-on-off-cpu': { 455 'cpu-clock:u': (299, 396124304), 456 'sched:sched_switch': (0, 0), 457 }, 458 } 459 460 proto_file_path = self.convert_perf_data_to_proto_file( 461 TestHelper.testdata_path('perf_with_trace_offcpu_v2.data')) 462 report_lib.SetRecordFile(proto_file_path) 463 self.assertEqual(set(report_lib.GetSupportedTraceOffCpuModes()), set(mode_dict.keys())) 464 for mode, expected_values in mode_dict.items(): 465 report_lib.Close() 466 report_lib = ProtoFileReportLib() 467 report_lib.SetRecordFile(proto_file_path) 468 report_lib.SetTraceOffCpuMode(mode) 469 470 cpu_clock_period = 0 471 cpu_clock_samples = 0 472 sched_switch_period = 0 473 sched_switch_samples = 0 474 while report_lib.GetNextSample(): 475 sample = report_lib.GetCurrentSample() 476 event = report_lib.GetEventOfCurrentSample() 477 if event.name == 'cpu-clock:u': 478 cpu_clock_period += sample.period 479 cpu_clock_samples += 1 480 else: 481 self.assertEqual(event.name, 'sched:sched_switch') 482 sched_switch_period += sample.period 483 sched_switch_samples += 1 484 self.assertEqual(cpu_clock_samples, expected_values['cpu-clock:u'][0]) 485 self.assertEqual(cpu_clock_period, expected_values['cpu-clock:u'][1]) 486 self.assertEqual(sched_switch_samples, expected_values['sched:sched_switch'][0]) 487 self.assertEqual(sched_switch_period, expected_values['sched:sched_switch'][1]) 488 489 # Check trace-offcpu modes on a profile not recorded with --trace-offcpu. 490 report_lib.Close() 491 report_lib = ProtoFileReportLib() 492 proto_file_path = self.convert_perf_data_to_proto_file( 493 TestHelper.testdata_path('perf.data')) 494 report_lib.SetRecordFile(proto_file_path) 495 self.assertEqual(report_lib.GetSupportedTraceOffCpuModes(), []) 496