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 collections 18import json 19from typing import Any, Dict, List 20 21from binary_cache_builder import BinaryCacheBuilder 22from . test_utils import TestBase, TestHelper 23 24 25class TestReportHtml(TestBase): 26 def test_long_callchain(self): 27 self.run_cmd(['report_html.py', '-i', 28 TestHelper.testdata_path('perf_with_long_callchain.data')]) 29 30 def test_aggregated_by_thread_name(self): 31 # Calculate event_count for each thread name before aggregation. 32 event_count_for_thread_name = collections.defaultdict(lambda: 0) 33 # use "--min_func_percent 0" to avoid cutting any thread. 34 record_data = self.get_record_data(['--min_func_percent', '0', '-i', 35 TestHelper.testdata_path('aggregatable_perf1.data'), 36 TestHelper.testdata_path('aggregatable_perf2.data')]) 37 event = record_data['sampleInfo'][0] 38 for process in event['processes']: 39 for thread in process['threads']: 40 thread_name = record_data['threadNames'][str(thread['tid'])] 41 event_count_for_thread_name[thread_name] += thread['eventCount'] 42 43 # Check event count for each thread after aggregation. 44 record_data = self.get_record_data(['--aggregate-by-thread-name', 45 '--min_func_percent', '0', '-i', 46 TestHelper.testdata_path('aggregatable_perf1.data'), 47 TestHelper.testdata_path('aggregatable_perf2.data')]) 48 event = record_data['sampleInfo'][0] 49 hit_count = 0 50 for process in event['processes']: 51 for thread in process['threads']: 52 thread_name = record_data['threadNames'][str(thread['tid'])] 53 self.assertEqual(thread['eventCount'], 54 event_count_for_thread_name[thread_name]) 55 hit_count += 1 56 self.assertEqual(hit_count, len(event_count_for_thread_name)) 57 58 def test_no_empty_process(self): 59 """ Test not showing a process having no threads. """ 60 perf_data = TestHelper.testdata_path('two_process_perf.data') 61 record_data = self.get_record_data(['-i', perf_data]) 62 processes = record_data['sampleInfo'][0]['processes'] 63 self.assertEqual(len(processes), 2) 64 65 # One process is removed because all its threads are removed for not 66 # reaching the min_func_percent limit. 67 record_data = self.get_record_data(['-i', perf_data, '--min_func_percent', '20']) 68 processes = record_data['sampleInfo'][0]['processes'] 69 self.assertEqual(len(processes), 1) 70 71 def test_proguard_mapping_file(self): 72 """ Test --proguard-mapping-file option. """ 73 testdata_file = TestHelper.testdata_path('perf_need_proguard_mapping.data') 74 proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt') 75 original_methodname = 'androidx.fragment.app.FragmentActivity.startActivityForResult' 76 # Can't show original method name without proguard mapping file. 77 record_data = self.get_record_data(['-i', testdata_file]) 78 self.assertNotIn(original_methodname, json.dumps(record_data)) 79 # Show original method name with proguard mapping file. 80 record_data = self.get_record_data( 81 ['-i', testdata_file, '--proguard-mapping-file', proguard_mapping_file]) 82 self.assertIn(original_methodname, json.dumps(record_data)) 83 84 def get_record_data(self, options: List[str]) -> Dict[str, Any]: 85 args = ['report_html.py'] + options 86 if TestHelper.ndk_path: 87 args += ['--ndk_path', TestHelper.ndk_path] 88 self.run_cmd(args) 89 with open('report.html', 'r') as fh: 90 data = fh.read() 91 start_str = 'type="application/json"' 92 end_str = '</script>' 93 start_pos = data.find(start_str) 94 self.assertNotEqual(start_pos, -1) 95 start_pos = data.find('>', start_pos) 96 self.assertNotEqual(start_pos, -1) 97 start_pos += 1 98 end_pos = data.find(end_str, start_pos) 99 self.assertNotEqual(end_pos, -1) 100 json_data = data[start_pos:end_pos] 101 return json.loads(json_data) 102 103 def test_add_source_code(self): 104 """ Test --add_source_code option. """ 105 testdata_file = TestHelper.testdata_path('runtest_two_functions_arm64_perf.data') 106 107 # Build binary_cache. 108 binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False) 109 binary_cache_builder.build_binary_cache(testdata_file, [TestHelper.testdata_dir]) 110 111 # Generate report.html. 112 source_dir = TestHelper.testdata_dir 113 record_data = self.get_record_data( 114 ['-i', testdata_file, '--add_source_code', '--source_dirs', str(source_dir)]) 115 116 # Check source code info in samples. 117 source_code_list = [] 118 thread = record_data['sampleInfo'][0]['processes'][0]['threads'][0] 119 for lib in thread['libs']: 120 for function in lib['functions']: 121 for source_code_info in function.get('s') or []: 122 source_file = record_data['sourceFiles'][source_code_info['f']] 123 file_path = source_file['path'] 124 line_number = source_code_info['l'] 125 line_content = source_file['code'][str(line_number)] 126 event_count = source_code_info['e'] 127 subtree_event_count = source_code_info['s'] 128 s = (f'{file_path}:{line_number}:{line_content}:' + 129 f'{event_count}:{subtree_event_count}') 130 source_code_list.append(s) 131 check_items = ['two_functions.cpp:9: *p = i;\n:590184:590184', 132 'two_functions.cpp:16: *p = i;\n:591577:591577', 133 'two_functions.cpp:22: Function1();\n:0:590184', 134 'two_functions.cpp:23: Function2();\n:0:591577'] 135 for item in check_items: 136 found = False 137 for source_code in source_code_list: 138 if item in source_code: 139 found = True 140 break 141 self.assertTrue(found, item) 142 143 def test_add_disassembly(self): 144 """ Test --add_disassembly option. """ 145 testdata_file = TestHelper.testdata_path('runtest_two_functions_arm64_perf.data') 146 147 # Build binary_cache. 148 binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False) 149 binary_cache_builder.build_binary_cache(testdata_file, [TestHelper.testdata_dir]) 150 151 # Generate report.html. 152 record_data = self.get_record_data(['-i', testdata_file, '--add_disassembly']) 153 154 # Check disassembly in samples. 155 disassembly_list = [] 156 thread = record_data['sampleInfo'][0]['processes'][0]['threads'][0] 157 for lib in thread['libs']: 158 lib_name = record_data['libList'][lib['libId']] 159 for function in lib['functions']: 160 for addr_info in function.get('a') or []: 161 addr = addr_info['a'] 162 event_count = addr_info['e'] 163 subtree_event_count = addr_info['s'] 164 function_data = record_data['functionMap'][str(function['f'])] 165 function_name = function_data['f'] 166 for dis_line, dis_addr in function_data.get('d') or []: 167 if addr == dis_addr: 168 s = (f'{lib_name}:{function_name}:{addr}:' + 169 f'{event_count}:{subtree_event_count}') 170 disassembly_list.append(s) 171 172 check_items = ['simpleperf_runtest_two_functions_arm64:Function1():0x1094:590184:590184', 173 'simpleperf_runtest_two_functions_arm64:Function2():0x1104:591577:591577', 174 'simpleperf_runtest_two_functions_arm64:main:0x113c:0:590184', 175 'simpleperf_runtest_two_functions_arm64:main:0x1140:0:591577'] 176 for item in check_items: 177 found = False 178 for disassembly in disassembly_list: 179 if item in disassembly: 180 found = True 181 break 182 self.assertTrue(found, item) 183