You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
10 KiB
303 lines
10 KiB
#!/usr/bin/env python |
|
# |
|
# Copyright (C) 2016 The Android Open Source Project |
|
# |
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
|
# you may not use this file except in compliance with the License. |
|
# You may obtain a copy of the License at |
|
# |
|
# http://www.apache.org/licenses/LICENSE-2.0 |
|
# |
|
# Unless required by applicable law or agreed to in writing, software |
|
# distributed under the License is distributed on an "AS IS" BASIS, |
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
# See the License for the specific language governing permissions and |
|
# limitations under the License. |
|
# |
|
|
|
"""simpleperf_report_lib.py: a python wrapper of libsimpleperf_report.so. |
|
Used to access samples in perf.data. |
|
|
|
""" |
|
|
|
import ctypes as ct |
|
import os |
|
import subprocess |
|
import sys |
|
import unittest |
|
from utils import * |
|
|
|
|
|
def _get_native_lib(): |
|
return get_host_binary_path('libsimpleperf_report.so') |
|
|
|
|
|
def _is_null(p): |
|
return ct.cast(p, ct.c_void_p).value is None |
|
|
|
|
|
def _char_pt(str): |
|
return str_to_bytes(str) |
|
|
|
|
|
def _char_pt_to_str(char_pt): |
|
return bytes_to_str(char_pt) |
|
|
|
|
|
class SampleStruct(ct.Structure): |
|
_fields_ = [('ip', ct.c_uint64), |
|
('pid', ct.c_uint32), |
|
('tid', ct.c_uint32), |
|
('thread_comm', ct.c_char_p), |
|
('time', ct.c_uint64), |
|
('in_kernel', ct.c_uint32), |
|
('cpu', ct.c_uint32), |
|
('period', ct.c_uint64)] |
|
|
|
|
|
class EventStruct(ct.Structure): |
|
_fields_ = [('name', ct.c_char_p)] |
|
|
|
|
|
class MappingStruct(ct.Structure): |
|
_fields_ = [('start', ct.c_uint64), |
|
('end', ct.c_uint64), |
|
('pgoff', ct.c_uint64)] |
|
|
|
|
|
class SymbolStruct(ct.Structure): |
|
_fields_ = [('dso_name', ct.c_char_p), |
|
('vaddr_in_file', ct.c_uint64), |
|
('symbol_name', ct.c_char_p), |
|
('symbol_addr', ct.c_uint64), |
|
('mapping', ct.POINTER(MappingStruct))] |
|
|
|
|
|
class CallChainEntryStructure(ct.Structure): |
|
_fields_ = [('ip', ct.c_uint64), |
|
('symbol', SymbolStruct)] |
|
|
|
|
|
class CallChainStructure(ct.Structure): |
|
_fields_ = [('nr', ct.c_uint32), |
|
('entries', ct.POINTER(CallChainEntryStructure))] |
|
|
|
|
|
# convert char_p to str for python3. |
|
class SampleStructUsingStr(object): |
|
def __init__(self, sample): |
|
self.ip = sample.ip |
|
self.pid = sample.pid |
|
self.tid = sample.tid |
|
self.thread_comm = _char_pt_to_str(sample.thread_comm) |
|
self.time = sample.time |
|
self.in_kernel = sample.in_kernel |
|
self.cpu = sample.cpu |
|
self.period = sample.period |
|
|
|
|
|
class EventStructUsingStr(object): |
|
def __init__(self, event): |
|
self.name = _char_pt_to_str(event.name) |
|
|
|
|
|
class SymbolStructUsingStr(object): |
|
def __init__(self, symbol): |
|
self.dso_name = _char_pt_to_str(symbol.dso_name) |
|
self.vaddr_in_file = symbol.vaddr_in_file |
|
self.symbol_name = _char_pt_to_str(symbol.symbol_name) |
|
self.symbol_addr = symbol.symbol_addr |
|
self.mapping = symbol.mapping |
|
|
|
|
|
class CallChainEntryStructureUsingStr(object): |
|
def __init__(self, entry): |
|
self.ip = entry.ip |
|
self.symbol = SymbolStructUsingStr(entry.symbol) |
|
|
|
|
|
class CallChainStructureUsingStr(object): |
|
def __init__(self, callchain): |
|
self.nr = callchain.nr |
|
self.entries = [] |
|
for i in range(self.nr): |
|
self.entries.append(CallChainEntryStructureUsingStr(callchain.entries[i])) |
|
|
|
|
|
class ReportLibStructure(ct.Structure): |
|
_fields_ = [] |
|
|
|
|
|
class ReportLib(object): |
|
|
|
def __init__(self, native_lib_path=None): |
|
if native_lib_path is None: |
|
native_lib_path = _get_native_lib() |
|
|
|
self._load_dependent_lib() |
|
self._lib = ct.CDLL(native_lib_path) |
|
self._CreateReportLibFunc = self._lib.CreateReportLib |
|
self._CreateReportLibFunc.restype = ct.POINTER(ReportLibStructure) |
|
self._DestroyReportLibFunc = self._lib.DestroyReportLib |
|
self._SetLogSeverityFunc = self._lib.SetLogSeverity |
|
self._SetSymfsFunc = self._lib.SetSymfs |
|
self._SetRecordFileFunc = self._lib.SetRecordFile |
|
self._SetKallsymsFileFunc = self._lib.SetKallsymsFile |
|
self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol |
|
self._GetNextSampleFunc = self._lib.GetNextSample |
|
self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct) |
|
self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample |
|
self._GetEventOfCurrentSampleFunc.restype = ct.POINTER(EventStruct) |
|
self._GetSymbolOfCurrentSampleFunc = self._lib.GetSymbolOfCurrentSample |
|
self._GetSymbolOfCurrentSampleFunc.restype = ct.POINTER(SymbolStruct) |
|
self._GetCallChainOfCurrentSampleFunc = self._lib.GetCallChainOfCurrentSample |
|
self._GetCallChainOfCurrentSampleFunc.restype = ct.POINTER( |
|
CallChainStructure) |
|
self._GetBuildIdForPathFunc = self._lib.GetBuildIdForPath |
|
self._GetBuildIdForPathFunc.restype = ct.c_char_p |
|
self._instance = self._CreateReportLibFunc() |
|
assert(not _is_null(self._instance)) |
|
|
|
self.convert_to_str = (sys.version_info >= (3, 0)) |
|
|
|
def _load_dependent_lib(self): |
|
# As the windows dll is built with mingw we need to load "libwinpthread-1.dll". |
|
if is_windows(): |
|
self._libwinpthread = ct.CDLL(get_host_binary_path('libwinpthread-1.dll')) |
|
|
|
def Close(self): |
|
if self._instance is None: |
|
return |
|
self._DestroyReportLibFunc(self._instance) |
|
self._instance = None |
|
|
|
def SetLogSeverity(self, log_level='info'): |
|
""" Set log severity of native lib, can be verbose,debug,info,error,fatal.""" |
|
cond = self._SetLogSeverityFunc(self.getInstance(), _char_pt(log_level)) |
|
self._check(cond, "Failed to set log level") |
|
|
|
def SetSymfs(self, symfs_dir): |
|
""" Set directory used to find symbols.""" |
|
cond = self._SetSymfsFunc(self.getInstance(), _char_pt(symfs_dir)) |
|
self._check(cond, "Failed to set symbols directory") |
|
|
|
def SetRecordFile(self, record_file): |
|
""" Set the path of record file, like perf.data.""" |
|
cond = self._SetRecordFileFunc(self.getInstance(), _char_pt(record_file)) |
|
self._check(cond, "Failed to set record file") |
|
|
|
def ShowIpForUnknownSymbol(self): |
|
self._ShowIpForUnknownSymbolFunc(self.getInstance()) |
|
|
|
def SetKallsymsFile(self, kallsym_file): |
|
""" Set the file path to a copy of the /proc/kallsyms file (for off device decoding) """ |
|
cond = self._SetKallsymsFileFunc(self.getInstance(), _char_pt(kallsym_file)) |
|
self._check(cond, "Failed to set kallsyms file") |
|
|
|
def GetNextSample(self): |
|
sample = self._GetNextSampleFunc(self.getInstance()) |
|
if _is_null(sample): |
|
return None |
|
if self.convert_to_str: |
|
return SampleStructUsingStr(sample[0]) |
|
return sample[0] |
|
|
|
def GetEventOfCurrentSample(self): |
|
event = self._GetEventOfCurrentSampleFunc(self.getInstance()) |
|
assert(not _is_null(event)) |
|
if self.convert_to_str: |
|
return EventStructUsingStr(event[0]) |
|
return event[0] |
|
|
|
def GetSymbolOfCurrentSample(self): |
|
symbol = self._GetSymbolOfCurrentSampleFunc(self.getInstance()) |
|
assert(not _is_null(symbol)) |
|
if self.convert_to_str: |
|
return SymbolStructUsingStr(symbol[0]) |
|
return symbol[0] |
|
|
|
def GetCallChainOfCurrentSample(self): |
|
callchain = self._GetCallChainOfCurrentSampleFunc(self.getInstance()) |
|
assert(not _is_null(callchain)) |
|
if self.convert_to_str: |
|
return CallChainStructureUsingStr(callchain[0]) |
|
return callchain[0] |
|
|
|
def GetBuildIdForPath(self, path): |
|
build_id = self._GetBuildIdForPathFunc(self.getInstance(), _char_pt(path)) |
|
assert(not _is_null(build_id)) |
|
return _char_pt_to_str(build_id) |
|
|
|
def getInstance(self): |
|
if self._instance is None: |
|
raise Exception("Instance is Closed") |
|
return self._instance |
|
|
|
def _check(self, cond, failmsg): |
|
if not cond: |
|
raise Exception(failmsg) |
|
|
|
|
|
class TestReportLib(unittest.TestCase): |
|
def setUp(self): |
|
self.perf_data_path = os.path.join(os.path.dirname(get_script_dir()), |
|
'testdata', 'perf_with_symbols.data') |
|
if not os.path.isfile(self.perf_data_path): |
|
raise Exception("can't find perf_data at %s" % self.perf_data_path) |
|
self.report_lib = ReportLib() |
|
self.report_lib.SetRecordFile(self.perf_data_path) |
|
|
|
def tearDown(self): |
|
self.report_lib.Close() |
|
|
|
def test_build_id(self): |
|
build_id = self.report_lib.GetBuildIdForPath('/data/t2') |
|
self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') |
|
|
|
def test_symbol_addr(self): |
|
found_func2 = False |
|
while True: |
|
sample = self.report_lib.GetNextSample() |
|
if sample is None: |
|
break |
|
symbol = self.report_lib.GetSymbolOfCurrentSample() |
|
if symbol.symbol_name == 'func2(int, int)': |
|
found_func2 = True |
|
self.assertEqual(symbol.symbol_addr, 0x4004ed) |
|
self.assertTrue(found_func2) |
|
|
|
def test_sample(self): |
|
found_sample = False |
|
while True: |
|
sample = self.report_lib.GetNextSample() |
|
if sample is None: |
|
break |
|
if sample.ip == 0x4004ff and sample.time == 7637889424953: |
|
found_sample = True |
|
self.assertEqual(sample.pid, 15926) |
|
self.assertEqual(sample.tid, 15926) |
|
self.assertEqual(sample.thread_comm, 't2') |
|
self.assertEqual(sample.cpu, 5) |
|
self.assertEqual(sample.period, 694614) |
|
event = self.report_lib.GetEventOfCurrentSample() |
|
self.assertEqual(event.name, 'cpu-cycles') |
|
callchain = self.report_lib.GetCallChainOfCurrentSample() |
|
self.assertEqual(callchain.nr, 0) |
|
self.assertTrue(found_sample) |
|
|
|
|
|
def main(): |
|
test_all = True |
|
if len(sys.argv) > 1 and sys.argv[1] == '--test-one': |
|
test_all = False |
|
del sys.argv[1] |
|
|
|
if test_all: |
|
subprocess.check_call(['python', os.path.realpath(__file__), '--test-one']) |
|
subprocess.check_call(['python3', os.path.realpath(__file__), '--test-one']) |
|
else: |
|
sys.exit(unittest.main()) |
|
|
|
|
|
if __name__ == '__main__': |
|
main() |