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