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