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