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.
		
		
		
		
			
				
					371 lines
				
				14 KiB
			
		
		
			
		
	
	
					371 lines
				
				14 KiB
			| 
											6 years ago
										 | #!/usr/bin/env python3
 | ||
|  | #
 | ||
|  | # 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 program counter of the thread generating the sample.
 | ||
|  |         pid: process id (or thread group id) of the thread generating the sample.
 | ||
|  |         tid: thread id.
 | ||
|  |         thread_comm: thread name.
 | ||
|  |         time: time at which the sample was generated. The value is in nanoseconds.
 | ||
|  |               The clock is decided by the --clockid option in `simpleperf record`.
 | ||
|  |         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/<pid>/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._ShowArtFramesFunc = self._lib.ShowArtFrames
 | ||
|  |         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 ShowArtFrames(self, show=True):
 | ||
|  |         """ Show frames of internal methods of the Java interpreter. """
 | ||
|  |         self._ShowArtFramesFunc(self.getInstance(), show)
 | ||
|  | 
 | ||
|  |     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)
 |