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