#!/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): if p: return False return ct.cast(p, ct.c_void_p).value is None def _char_pt(s): return str_to_bytes(s) def _char_pt_to_str(char_pt): return bytes_to_str(char_pt) class SampleStruct(ct.Structure): """ Instance of a sample in perf.data. ip: the address of the instruction the cpu is running when the sample happens. pid: process id (or thread group id) of the thread generating the sample. tid: thread id. thread_comm: thread name. time: timestamp of a sample, the meaning is decided by simpleperf record --clockid option. in_kernel: whether the instruction is in kernel space or user space. cpu: the cpu generating the sample. period: count of events have happened since last sample. For example, if we use -e cpu-cycles, it means how many cpu-cycles have happened. If we use -e cpu-clock, it means how many nanoseconds have passed. """ _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): """ Name of the event. """ _fields_ = [('name', ct.c_char_p)] class MappingStruct(ct.Structure): """ A mapping area in the monitored threads, like the content in /proc//maps. start: start addr in memory. end: end addr in memory. pgoff: offset in the mapped shared library. """ _fields_ = [('start', ct.c_uint64), ('end', ct.c_uint64), ('pgoff', ct.c_uint64)] class SymbolStruct(ct.Structure): """ Symbol info of the instruction hit by a sample or a callchain entry of a sample. dso_name: path of the shared library containing the instruction. vaddr_in_file: virtual address of the instruction in the shared library. symbol_name: name of the function containing the instruction. symbol_addr: start addr of the function containing the instruction. symbol_len: length of the function in the shared library. mapping: the mapping area hit by the instruction. """ _fields_ = [('dso_name', ct.c_char_p), ('vaddr_in_file', ct.c_uint64), ('symbol_name', ct.c_char_p), ('symbol_addr', ct.c_uint64), ('symbol_len', ct.c_uint64), ('mapping', ct.POINTER(MappingStruct))] class CallChainEntryStructure(ct.Structure): """ A callchain entry of a sample. ip: the address of the instruction of the callchain entry. symbol: symbol info of the callchain entry. """ _fields_ = [('ip', ct.c_uint64), ('symbol', SymbolStruct)] class CallChainStructure(ct.Structure): """ Callchain info of a sample. nr: number of entries in the callchain. entries: a pointer to an array of CallChainEntryStructure. For example, if a sample is generated when a thread is running function C with callchain function A -> function B -> function C. Then nr = 2, and entries = [function B, function A]. """ _fields_ = [('nr', ct.c_uint32), ('entries', ct.POINTER(CallChainEntryStructure))] class FeatureSectionStructure(ct.Structure): """ A feature section in perf.data to store information like record cmd, device arch, etc. data: a pointer to a buffer storing the section data. data_size: data size in bytes. """ _fields_ = [('data', ct.POINTER(ct.c_char)), ('data_size', ct.c_uint32)] # 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._GetFeatureSection = self._lib.GetFeatureSection self._GetFeatureSection.restype = ct.POINTER(FeatureSectionStructure) self._instance = self._CreateReportLibFunc() assert not _is_null(self._instance) self.convert_to_str = (sys.version_info >= (3, 0)) self.meta_info = None self.current_sample = None self.record_cmd = None 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): psample = self._GetNextSampleFunc(self.getInstance()) if _is_null(psample): self.current_sample = None else: sample = psample[0] self.current_sample = SampleStructUsingStr(sample) if self.convert_to_str else sample return self.current_sample def GetCurrentSample(self): return self.current_sample 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 GetRecordCmd(self): if self.record_cmd is not None: return self.record_cmd self.record_cmd = '' feature_data = self._GetFeatureSection(self.getInstance(), _char_pt('cmdline')) if not _is_null(feature_data): void_p = ct.cast(feature_data[0].data, ct.c_void_p) arg_count = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value void_p.value += 4 args = [] for _ in range(arg_count): str_len = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value void_p.value += 4 char_p = ct.cast(void_p, ct.POINTER(ct.c_char)) current_str = '' for j in range(str_len): c = bytes_to_str(char_p[j]) if c != '\0': current_str += c if ' ' in current_str: current_str = '"' + current_str + '"' args.append(current_str) void_p.value += str_len self.record_cmd = ' '.join(args) return self.record_cmd def _GetFeatureString(self, feature_name): feature_data = self._GetFeatureSection(self.getInstance(), _char_pt(feature_name)) result = '' if not _is_null(feature_data): void_p = ct.cast(feature_data[0].data, ct.c_void_p) str_len = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value void_p.value += 4 char_p = ct.cast(void_p, ct.POINTER(ct.c_char)) for i in range(str_len): c = bytes_to_str(char_p[i]) if c == '\0': break result += c return result def GetArch(self): return self._GetFeatureString('arch') def MetaInfo(self): """ Return a string to string map stored in meta_info section in perf.data. It is used to pass some short meta information. """ if self.meta_info is None: self.meta_info = {} feature_data = self._GetFeatureSection(self.getInstance(), _char_pt('meta_info')) if not _is_null(feature_data): str_list = [] data = feature_data[0].data data_size = feature_data[0].data_size current_str = '' for i in range(data_size): c = bytes_to_str(data[i]) if c != '\0': current_str += c else: str_list.append(current_str) current_str = '' for i in range(0, len(str_list), 2): self.meta_info[str_list[i]] = str_list[i + 1] return self.meta_info 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)