• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright (C) 2016 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#
17
18"""simpleperf_report_lib.py: a python wrapper of libsimpleperf_report.so.
19   Used to access samples in perf.data.
20
21"""
22
23import ctypes as ct
24import os
25import subprocess
26import sys
27import unittest
28from utils import *
29
30
31def _get_native_lib():
32    return get_host_binary_path('libsimpleperf_report.so')
33
34
35def _is_null(p):
36    return ct.cast(p, ct.c_void_p).value is None
37
38
39def _char_pt(str):
40    return str_to_bytes(str)
41
42
43def _char_pt_to_str(char_pt):
44    return bytes_to_str(char_pt)
45
46
47class SampleStruct(ct.Structure):
48    _fields_ = [('ip', ct.c_uint64),
49                ('pid', ct.c_uint32),
50                ('tid', ct.c_uint32),
51                ('thread_comm', ct.c_char_p),
52                ('time', ct.c_uint64),
53                ('in_kernel', ct.c_uint32),
54                ('cpu', ct.c_uint32),
55                ('period', ct.c_uint64)]
56
57
58class EventStruct(ct.Structure):
59    _fields_ = [('name', ct.c_char_p)]
60
61
62class MappingStruct(ct.Structure):
63    _fields_ = [('start', ct.c_uint64),
64                ('end', ct.c_uint64),
65                ('pgoff', ct.c_uint64)]
66
67
68class SymbolStruct(ct.Structure):
69    _fields_ = [('dso_name', ct.c_char_p),
70                ('vaddr_in_file', ct.c_uint64),
71                ('symbol_name', ct.c_char_p),
72                ('symbol_addr', ct.c_uint64),
73                ('mapping', ct.POINTER(MappingStruct))]
74
75
76class CallChainEntryStructure(ct.Structure):
77    _fields_ = [('ip', ct.c_uint64),
78                ('symbol', SymbolStruct)]
79
80
81class CallChainStructure(ct.Structure):
82    _fields_ = [('nr', ct.c_uint32),
83                ('entries', ct.POINTER(CallChainEntryStructure))]
84
85
86# convert char_p to str for python3.
87class SampleStructUsingStr(object):
88    def __init__(self, sample):
89        self.ip = sample.ip
90        self.pid = sample.pid
91        self.tid = sample.tid
92        self.thread_comm = _char_pt_to_str(sample.thread_comm)
93        self.time = sample.time
94        self.in_kernel = sample.in_kernel
95        self.cpu = sample.cpu
96        self.period = sample.period
97
98
99class EventStructUsingStr(object):
100    def __init__(self, event):
101        self.name = _char_pt_to_str(event.name)
102
103
104class SymbolStructUsingStr(object):
105    def __init__(self, symbol):
106        self.dso_name = _char_pt_to_str(symbol.dso_name)
107        self.vaddr_in_file = symbol.vaddr_in_file
108        self.symbol_name = _char_pt_to_str(symbol.symbol_name)
109        self.symbol_addr = symbol.symbol_addr
110        self.mapping = symbol.mapping
111
112
113class CallChainEntryStructureUsingStr(object):
114    def __init__(self, entry):
115        self.ip = entry.ip
116        self.symbol = SymbolStructUsingStr(entry.symbol)
117
118
119class CallChainStructureUsingStr(object):
120    def __init__(self, callchain):
121        self.nr = callchain.nr
122        self.entries = []
123        for i in range(self.nr):
124            self.entries.append(CallChainEntryStructureUsingStr(callchain.entries[i]))
125
126
127class ReportLibStructure(ct.Structure):
128    _fields_ = []
129
130
131class ReportLib(object):
132
133    def __init__(self, native_lib_path=None):
134        if native_lib_path is None:
135            native_lib_path = _get_native_lib()
136
137        self._load_dependent_lib()
138        self._lib = ct.CDLL(native_lib_path)
139        self._CreateReportLibFunc = self._lib.CreateReportLib
140        self._CreateReportLibFunc.restype = ct.POINTER(ReportLibStructure)
141        self._DestroyReportLibFunc = self._lib.DestroyReportLib
142        self._SetLogSeverityFunc = self._lib.SetLogSeverity
143        self._SetSymfsFunc = self._lib.SetSymfs
144        self._SetRecordFileFunc = self._lib.SetRecordFile
145        self._SetKallsymsFileFunc = self._lib.SetKallsymsFile
146        self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol
147        self._GetNextSampleFunc = self._lib.GetNextSample
148        self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct)
149        self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample
150        self._GetEventOfCurrentSampleFunc.restype = ct.POINTER(EventStruct)
151        self._GetSymbolOfCurrentSampleFunc = self._lib.GetSymbolOfCurrentSample
152        self._GetSymbolOfCurrentSampleFunc.restype = ct.POINTER(SymbolStruct)
153        self._GetCallChainOfCurrentSampleFunc = self._lib.GetCallChainOfCurrentSample
154        self._GetCallChainOfCurrentSampleFunc.restype = ct.POINTER(
155            CallChainStructure)
156        self._GetBuildIdForPathFunc = self._lib.GetBuildIdForPath
157        self._GetBuildIdForPathFunc.restype = ct.c_char_p
158        self._instance = self._CreateReportLibFunc()
159        assert(not _is_null(self._instance))
160
161        self.convert_to_str = (sys.version_info >= (3, 0))
162
163    def _load_dependent_lib(self):
164        # As the windows dll is built with mingw we need to load "libwinpthread-1.dll".
165        if is_windows():
166            self._libwinpthread = ct.CDLL(get_host_binary_path('libwinpthread-1.dll'))
167
168    def Close(self):
169        if self._instance is None:
170            return
171        self._DestroyReportLibFunc(self._instance)
172        self._instance = None
173
174    def SetLogSeverity(self, log_level='info'):
175        """ Set log severity of native lib, can be verbose,debug,info,error,fatal."""
176        cond = self._SetLogSeverityFunc(self.getInstance(), _char_pt(log_level))
177        self._check(cond, "Failed to set log level")
178
179    def SetSymfs(self, symfs_dir):
180        """ Set directory used to find symbols."""
181        cond = self._SetSymfsFunc(self.getInstance(), _char_pt(symfs_dir))
182        self._check(cond, "Failed to set symbols directory")
183
184    def SetRecordFile(self, record_file):
185        """ Set the path of record file, like perf.data."""
186        cond = self._SetRecordFileFunc(self.getInstance(), _char_pt(record_file))
187        self._check(cond, "Failed to set record file")
188
189    def ShowIpForUnknownSymbol(self):
190        self._ShowIpForUnknownSymbolFunc(self.getInstance())
191
192    def SetKallsymsFile(self, kallsym_file):
193        """ Set the file path to a copy of the /proc/kallsyms file (for off device decoding) """
194        cond = self._SetKallsymsFileFunc(self.getInstance(), _char_pt(kallsym_file))
195        self._check(cond, "Failed to set kallsyms file")
196
197    def GetNextSample(self):
198        sample = self._GetNextSampleFunc(self.getInstance())
199        if _is_null(sample):
200            return None
201        if self.convert_to_str:
202            return SampleStructUsingStr(sample[0])
203        return sample[0]
204
205    def GetEventOfCurrentSample(self):
206        event = self._GetEventOfCurrentSampleFunc(self.getInstance())
207        assert(not _is_null(event))
208        if self.convert_to_str:
209            return EventStructUsingStr(event[0])
210        return event[0]
211
212    def GetSymbolOfCurrentSample(self):
213        symbol = self._GetSymbolOfCurrentSampleFunc(self.getInstance())
214        assert(not _is_null(symbol))
215        if self.convert_to_str:
216            return SymbolStructUsingStr(symbol[0])
217        return symbol[0]
218
219    def GetCallChainOfCurrentSample(self):
220        callchain = self._GetCallChainOfCurrentSampleFunc(self.getInstance())
221        assert(not _is_null(callchain))
222        if self.convert_to_str:
223            return CallChainStructureUsingStr(callchain[0])
224        return callchain[0]
225
226    def GetBuildIdForPath(self, path):
227        build_id = self._GetBuildIdForPathFunc(self.getInstance(), _char_pt(path))
228        assert(not _is_null(build_id))
229        return _char_pt_to_str(build_id)
230
231    def getInstance(self):
232        if self._instance is None:
233            raise Exception("Instance is Closed")
234        return self._instance
235
236    def _check(self, cond, failmsg):
237        if not cond:
238            raise Exception(failmsg)
239
240
241class TestReportLib(unittest.TestCase):
242    def setUp(self):
243        self.perf_data_path = os.path.join(os.path.dirname(get_script_dir()),
244                                           'testdata', 'perf_with_symbols.data')
245        if not os.path.isfile(self.perf_data_path):
246            raise Exception("can't find perf_data at %s" % self.perf_data_path)
247        self.report_lib = ReportLib()
248        self.report_lib.SetRecordFile(self.perf_data_path)
249
250    def tearDown(self):
251        self.report_lib.Close()
252
253    def test_build_id(self):
254        build_id = self.report_lib.GetBuildIdForPath('/data/t2')
255        self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de')
256
257    def test_symbol_addr(self):
258        found_func2 = False
259        while True:
260            sample = self.report_lib.GetNextSample()
261            if sample is None:
262                break
263            symbol = self.report_lib.GetSymbolOfCurrentSample()
264            if symbol.symbol_name == 'func2(int, int)':
265                found_func2 = True
266                self.assertEqual(symbol.symbol_addr, 0x4004ed)
267        self.assertTrue(found_func2)
268
269    def test_sample(self):
270        found_sample = False
271        while True:
272            sample = self.report_lib.GetNextSample()
273            if sample is None:
274                break
275            if sample.ip == 0x4004ff and sample.time == 7637889424953:
276                found_sample = True
277                self.assertEqual(sample.pid, 15926)
278                self.assertEqual(sample.tid, 15926)
279                self.assertEqual(sample.thread_comm, 't2')
280                self.assertEqual(sample.cpu, 5)
281                self.assertEqual(sample.period, 694614)
282                event = self.report_lib.GetEventOfCurrentSample()
283                self.assertEqual(event.name, 'cpu-cycles')
284                callchain = self.report_lib.GetCallChainOfCurrentSample()
285                self.assertEqual(callchain.nr, 0)
286        self.assertTrue(found_sample)
287
288
289def main():
290    test_all = True
291    if len(sys.argv) > 1 and sys.argv[1] == '--test-one':
292        test_all = False
293        del sys.argv[1]
294
295    if test_all:
296        subprocess.check_call(['python', os.path.realpath(__file__), '--test-one'])
297        subprocess.check_call(['python3', os.path.realpath(__file__), '--test-one'])
298    else:
299        sys.exit(unittest.main())
300
301
302if __name__ == '__main__':
303    main()