• 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 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