• 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 collections
24import ctypes as ct
25import struct
26from utils import bytes_to_str, get_host_binary_path, is_windows, str_to_bytes
27
28
29def _get_native_lib():
30    return get_host_binary_path('libsimpleperf_report.so')
31
32
33def _is_null(p):
34    if p:
35        return False
36    return ct.cast(p, ct.c_void_p).value is None
37
38
39def _char_pt(s):
40    return str_to_bytes(s)
41
42
43def _char_pt_to_str(char_pt):
44    return bytes_to_str(char_pt)
45
46def _check(cond, failmsg):
47    if not cond:
48        raise RuntimeError(failmsg)
49
50
51class SampleStruct(ct.Structure):
52    """ Instance of a sample in perf.data.
53        ip: the program counter of the thread generating the sample.
54        pid: process id (or thread group id) of the thread generating the sample.
55        tid: thread id.
56        thread_comm: thread name.
57        time: time at which the sample was generated. The value is in nanoseconds.
58              The clock is decided by the --clockid option in `simpleperf record`.
59        in_kernel: whether the instruction is in kernel space or user space.
60        cpu: the cpu generating the sample.
61        period: count of events have happened since last sample. For example, if we use
62             -e cpu-cycles, it means how many cpu-cycles have happened.
63             If we use -e cpu-clock, it means how many nanoseconds have passed.
64    """
65    _fields_ = [('ip', ct.c_uint64),
66                ('pid', ct.c_uint32),
67                ('tid', ct.c_uint32),
68                ('_thread_comm', ct.c_char_p),
69                ('time', ct.c_uint64),
70                ('in_kernel', ct.c_uint32),
71                ('cpu', ct.c_uint32),
72                ('period', ct.c_uint64)]
73
74    @property
75    def thread_comm(self):
76        return _char_pt_to_str(self._thread_comm)
77
78
79class TracingFieldFormatStruct(ct.Structure):
80    """Format of a tracing field.
81       name: name of the field.
82       offset: offset of the field in tracing data.
83       elem_size: size of the element type.
84       elem_count: the number of elements in this field, more than one if the field is an array.
85       is_signed: whether the element type is signed or unsigned.
86    """
87    _fields_ = [('_name', ct.c_char_p),
88                ('offset', ct.c_uint32),
89                ('elem_size', ct.c_uint32),
90                ('elem_count', ct.c_uint32),
91                ('is_signed', ct.c_uint32)]
92
93    _unpack_key_dict = {1: 'b', 2: 'h', 4: 'i', 8: 'q'}
94
95    @property
96    def name(self):
97        return _char_pt_to_str(self._name)
98
99    def parse_value(self, data):
100        """ Parse value of a field in a tracepoint event.
101            The return value depends on the type of the field, and can be an int value, a string,
102            an array of int values, etc. If the type can't be parsed, return a byte array or an
103            array of byte arrays.
104        """
105        if self.elem_count > 1 and self.elem_size == 1 and self.is_signed == 0:
106            # The field is a string.
107            length = 0
108            while length < self.elem_count and bytes_to_str(data[self.offset + length]) != '\x00':
109                length += 1
110            return bytes_to_str(data[self.offset : self.offset + length])
111        unpack_key = self._unpack_key_dict.get(self.elem_size)
112        if unpack_key:
113            if not self.is_signed:
114                unpack_key = unpack_key.upper()
115            value = struct.unpack('%d%s' % (self.elem_count, unpack_key),
116                                  data[self.offset:self.offset + self.elem_count * self.elem_size])
117        else:
118            # Since we don't know the element type, just return the bytes.
119            value = []
120            offset = self.offset
121            for _ in range(self.elem_count):
122                value.append(data[offset : offset + self.elem_size])
123                offset += self.elem_size
124        if self.elem_count == 1:
125            value = value[0]
126        return value
127
128
129class TracingDataFormatStruct(ct.Structure):
130    """Format of tracing data of a tracepoint event, like
131       https://www.kernel.org/doc/html/latest/trace/events.html#event-formats.
132       size: total size of all fields in the tracing data.
133       field_count: the number of fields.
134       fields: an array of fields.
135    """
136    _fields_ = [('size', ct.c_uint32),
137                ('field_count', ct.c_uint32),
138                ('fields', ct.POINTER(TracingFieldFormatStruct))]
139
140
141class EventStruct(ct.Structure):
142    """Event type of a sample.
143       name: name of the event type.
144       tracing_data_format: only available when it is a tracepoint event.
145    """
146    _fields_ = [('_name', ct.c_char_p),
147                ('tracing_data_format', TracingDataFormatStruct)]
148
149    @property
150    def name(self):
151        return _char_pt_to_str(self._name)
152
153
154class MappingStruct(ct.Structure):
155    """ A mapping area in the monitored threads, like the content in /proc/<pid>/maps.
156        start: start addr in memory.
157        end: end addr in memory.
158        pgoff: offset in the mapped shared library.
159    """
160    _fields_ = [('start', ct.c_uint64),
161                ('end', ct.c_uint64),
162                ('pgoff', ct.c_uint64)]
163
164
165class SymbolStruct(ct.Structure):
166    """ Symbol info of the instruction hit by a sample or a callchain entry of a sample.
167        dso_name: path of the shared library containing the instruction.
168        vaddr_in_file: virtual address of the instruction in the shared library.
169        symbol_name: name of the function containing the instruction.
170        symbol_addr: start addr of the function containing the instruction.
171        symbol_len: length of the function in the shared library.
172        mapping: the mapping area hit by the instruction.
173    """
174    _fields_ = [('_dso_name', ct.c_char_p),
175                ('vaddr_in_file', ct.c_uint64),
176                ('_symbol_name', ct.c_char_p),
177                ('symbol_addr', ct.c_uint64),
178                ('symbol_len', ct.c_uint64),
179                ('mapping', ct.POINTER(MappingStruct))]
180
181    @property
182    def dso_name(self):
183        return _char_pt_to_str(self._dso_name)
184
185    @property
186    def symbol_name(self):
187        return _char_pt_to_str(self._symbol_name)
188
189
190class CallChainEntryStructure(ct.Structure):
191    """ A callchain entry of a sample.
192        ip: the address of the instruction of the callchain entry.
193        symbol: symbol info of the callchain entry.
194    """
195    _fields_ = [('ip', ct.c_uint64),
196                ('symbol', SymbolStruct)]
197
198
199class CallChainStructure(ct.Structure):
200    """ Callchain info of a sample.
201        nr: number of entries in the callchain.
202        entries: a pointer to an array of CallChainEntryStructure.
203
204        For example, if a sample is generated when a thread is running function C
205        with callchain function A -> function B -> function C.
206        Then nr = 2, and entries = [function B, function A].
207    """
208    _fields_ = [('nr', ct.c_uint32),
209                ('entries', ct.POINTER(CallChainEntryStructure))]
210
211
212class FeatureSectionStructure(ct.Structure):
213    """ A feature section in perf.data to store information like record cmd, device arch, etc.
214        data: a pointer to a buffer storing the section data.
215        data_size: data size in bytes.
216    """
217    _fields_ = [('data', ct.POINTER(ct.c_char)),
218                ('data_size', ct.c_uint32)]
219
220
221class ReportLibStructure(ct.Structure):
222    _fields_ = []
223
224
225# pylint: disable=invalid-name
226class ReportLib(object):
227
228    def __init__(self, native_lib_path=None):
229        if native_lib_path is None:
230            native_lib_path = _get_native_lib()
231
232        self._load_dependent_lib()
233        self._lib = ct.CDLL(native_lib_path)
234        self._CreateReportLibFunc = self._lib.CreateReportLib
235        self._CreateReportLibFunc.restype = ct.POINTER(ReportLibStructure)
236        self._DestroyReportLibFunc = self._lib.DestroyReportLib
237        self._SetLogSeverityFunc = self._lib.SetLogSeverity
238        self._SetSymfsFunc = self._lib.SetSymfs
239        self._SetRecordFileFunc = self._lib.SetRecordFile
240        self._SetKallsymsFileFunc = self._lib.SetKallsymsFile
241        self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol
242        self._ShowArtFramesFunc = self._lib.ShowArtFrames
243        self._GetNextSampleFunc = self._lib.GetNextSample
244        self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct)
245        self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample
246        self._GetEventOfCurrentSampleFunc.restype = ct.POINTER(EventStruct)
247        self._GetSymbolOfCurrentSampleFunc = self._lib.GetSymbolOfCurrentSample
248        self._GetSymbolOfCurrentSampleFunc.restype = ct.POINTER(SymbolStruct)
249        self._GetCallChainOfCurrentSampleFunc = self._lib.GetCallChainOfCurrentSample
250        self._GetCallChainOfCurrentSampleFunc.restype = ct.POINTER(CallChainStructure)
251        self._GetTracingDataOfCurrentSampleFunc = self._lib.GetTracingDataOfCurrentSample
252        self._GetTracingDataOfCurrentSampleFunc.restype = ct.POINTER(ct.c_char)
253        self._GetBuildIdForPathFunc = self._lib.GetBuildIdForPath
254        self._GetBuildIdForPathFunc.restype = ct.c_char_p
255        self._GetFeatureSection = self._lib.GetFeatureSection
256        self._GetFeatureSection.restype = ct.POINTER(FeatureSectionStructure)
257        self._instance = self._CreateReportLibFunc()
258        assert not _is_null(self._instance)
259
260        self.meta_info = None
261        self.current_sample = None
262        self.record_cmd = None
263
264    def _load_dependent_lib(self):
265        # As the windows dll is built with mingw we need to load 'libwinpthread-1.dll'.
266        if is_windows():
267            self._libwinpthread = ct.CDLL(get_host_binary_path('libwinpthread-1.dll'))
268
269    def Close(self):
270        if self._instance is None:
271            return
272        self._DestroyReportLibFunc(self._instance)
273        self._instance = None
274
275    def SetLogSeverity(self, log_level='info'):
276        """ Set log severity of native lib, can be verbose,debug,info,error,fatal."""
277        cond = self._SetLogSeverityFunc(self.getInstance(), _char_pt(log_level))
278        _check(cond, 'Failed to set log level')
279
280    def SetSymfs(self, symfs_dir):
281        """ Set directory used to find symbols."""
282        cond = self._SetSymfsFunc(self.getInstance(), _char_pt(symfs_dir))
283        _check(cond, 'Failed to set symbols directory')
284
285    def SetRecordFile(self, record_file):
286        """ Set the path of record file, like perf.data."""
287        cond = self._SetRecordFileFunc(self.getInstance(), _char_pt(record_file))
288        _check(cond, 'Failed to set record file')
289
290    def ShowIpForUnknownSymbol(self):
291        self._ShowIpForUnknownSymbolFunc(self.getInstance())
292
293    def ShowArtFrames(self, show=True):
294        """ Show frames of internal methods of the Java interpreter. """
295        self._ShowArtFramesFunc(self.getInstance(), show)
296
297    def SetKallsymsFile(self, kallsym_file):
298        """ Set the file path to a copy of the /proc/kallsyms file (for off device decoding) """
299        cond = self._SetKallsymsFileFunc(self.getInstance(), _char_pt(kallsym_file))
300        _check(cond, 'Failed to set kallsyms file')
301
302    def GetNextSample(self):
303        psample = self._GetNextSampleFunc(self.getInstance())
304        if _is_null(psample):
305            self.current_sample = None
306        else:
307            self.current_sample = psample[0]
308        return self.current_sample
309
310    def GetCurrentSample(self):
311        return self.current_sample
312
313    def GetEventOfCurrentSample(self):
314        event = self._GetEventOfCurrentSampleFunc(self.getInstance())
315        assert not _is_null(event)
316        return event[0]
317
318    def GetSymbolOfCurrentSample(self):
319        symbol = self._GetSymbolOfCurrentSampleFunc(self.getInstance())
320        assert not _is_null(symbol)
321        return symbol[0]
322
323    def GetCallChainOfCurrentSample(self):
324        callchain = self._GetCallChainOfCurrentSampleFunc(self.getInstance())
325        assert not _is_null(callchain)
326        return callchain[0]
327
328    def GetTracingDataOfCurrentSample(self):
329        data = self._GetTracingDataOfCurrentSampleFunc(self.getInstance())
330        if _is_null(data):
331            return None
332        event = self.GetEventOfCurrentSample()
333        result = collections.OrderedDict()
334        for i in range(event.tracing_data_format.field_count):
335            field = event.tracing_data_format.fields[i]
336            result[field.name] = field.parse_value(data)
337        return result
338
339    def GetBuildIdForPath(self, path):
340        build_id = self._GetBuildIdForPathFunc(self.getInstance(), _char_pt(path))
341        assert not _is_null(build_id)
342        return _char_pt_to_str(build_id)
343
344    def GetRecordCmd(self):
345        if self.record_cmd is not None:
346            return self.record_cmd
347        self.record_cmd = ''
348        feature_data = self._GetFeatureSection(self.getInstance(), _char_pt('cmdline'))
349        if not _is_null(feature_data):
350            void_p = ct.cast(feature_data[0].data, ct.c_void_p)
351            arg_count = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value
352            void_p.value += 4
353            args = []
354            for _ in range(arg_count):
355                str_len = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value
356                void_p.value += 4
357                char_p = ct.cast(void_p, ct.POINTER(ct.c_char))
358                current_str = ''
359                for j in range(str_len):
360                    c = bytes_to_str(char_p[j])
361                    if c != '\0':
362                        current_str += c
363                if ' ' in current_str:
364                    current_str = '"' + current_str + '"'
365                args.append(current_str)
366                void_p.value += str_len
367            self.record_cmd = ' '.join(args)
368        return self.record_cmd
369
370    def _GetFeatureString(self, feature_name):
371        feature_data = self._GetFeatureSection(self.getInstance(), _char_pt(feature_name))
372        result = ''
373        if not _is_null(feature_data):
374            void_p = ct.cast(feature_data[0].data, ct.c_void_p)
375            str_len = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value
376            void_p.value += 4
377            char_p = ct.cast(void_p, ct.POINTER(ct.c_char))
378            for i in range(str_len):
379                c = bytes_to_str(char_p[i])
380                if c == '\0':
381                    break
382                result += c
383        return result
384
385    def GetArch(self):
386        return self._GetFeatureString('arch')
387
388    def MetaInfo(self):
389        """ Return a string to string map stored in meta_info section in perf.data.
390            It is used to pass some short meta information.
391        """
392        if self.meta_info is None:
393            self.meta_info = {}
394            feature_data = self._GetFeatureSection(self.getInstance(), _char_pt('meta_info'))
395            if not _is_null(feature_data):
396                str_list = []
397                data = feature_data[0].data
398                data_size = feature_data[0].data_size
399                current_str = ''
400                for i in range(data_size):
401                    c = bytes_to_str(data[i])
402                    if c != '\0':
403                        current_str += c
404                    else:
405                        str_list.append(current_str)
406                        current_str = ''
407                for i in range(0, len(str_list), 2):
408                    self.meta_info[str_list[i]] = str_list[i + 1]
409        return self.meta_info
410
411    def getInstance(self):
412        if self._instance is None:
413            raise Exception('Instance is Closed')
414        return self._instance
415