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()