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