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