• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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