blob: 3fdc234b76bd82d2a1decc5d434d3a20f19c5b58 [file] [log] [blame]
#!/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):
if sys.version_info < (3, 0):
return str
# In python 3, str are wide strings whereas the C api expects 8 bit strings, hence we have to convert
# For now using utf-8 as the encoding.
return str.encode('utf-8')
def _char_pt_to_str(char_pt):
if sys.version_info < (3, 0):
return char_pt
return char_pt.decode('utf-8')
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 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)]
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
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 also find "libwinpthread-1.dll".
# Load it before libsimpleperf_report.dll if it does exist in the same folder as this script.
if is_windows():
libwinpthread_path = os.path.join(get_script_dir(), "libwinpthread-1.dll")
if os.path.exists(libwinpthread_path):
self._libwinpthread = ct.CDLL(libwinpthread_path)
else:
log_fatal('%s is missing' % libwinpthread_path)
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()