256 lines
7.6 KiB
Python
256 lines
7.6 KiB
Python
#
|
|
# Copyright (C) International Business Machines Corp., 2009
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
# 2009-Dec-17: Initial version by Darren Hart <dvhltc@us.ibm.com>
|
|
#
|
|
|
|
from functools import update_wrapper
|
|
from ctracecmd import *
|
|
from UserDict import DictMixin
|
|
|
|
"""
|
|
Python interface to the tracecmd library for parsing ftrace traces
|
|
|
|
Python tracecmd applications should be written to this interface. It will be
|
|
updated as the tracecmd C API changes and try to minimze the impact to python
|
|
applications. The ctracecmd Python module is automatically generated using SWIG
|
|
and it is recommended applications not use it directly.
|
|
|
|
TODO: consider a complete class hierarchy of ftrace events...
|
|
"""
|
|
|
|
def cached_property(func, name=None):
|
|
if name is None:
|
|
name = func.__name__
|
|
def _get(self):
|
|
try:
|
|
return self.__cached_properties[name]
|
|
except AttributeError:
|
|
self.__cached_properties = {}
|
|
except KeyError:
|
|
pass
|
|
value = func(self)
|
|
self.__cached_properties[name] = value
|
|
return value
|
|
update_wrapper(_get, func)
|
|
def _del(self):
|
|
self.__cached_properties.pop(name, None)
|
|
return property(_get, None, _del)
|
|
|
|
class Event(object, DictMixin):
|
|
"""
|
|
This class can be used to access event data
|
|
according to an event's record and format.
|
|
"""
|
|
def __init__(self, pevent, record, format):
|
|
self._pevent = pevent
|
|
self._record = record
|
|
self._format = format
|
|
|
|
def __str__(self):
|
|
return "%d.%09d CPU%d %s: pid=%d comm=%s type=%d" % \
|
|
(self.ts/1000000000, self.ts%1000000000, self.cpu, self.name,
|
|
self.num_field("common_pid"), self.comm, self.type)
|
|
|
|
def __del__(self):
|
|
free_record(self._record)
|
|
|
|
def __getitem__(self, n):
|
|
f = tep_find_field(self._format, n)
|
|
if f is None:
|
|
raise KeyError("no field '%s'" % n)
|
|
return Field(self._record, f)
|
|
|
|
def keys(self):
|
|
return py_format_get_keys(self._format)
|
|
|
|
@cached_property
|
|
def comm(self):
|
|
return tep_data_comm_from_pid(self._pevent, self.pid)
|
|
|
|
@cached_property
|
|
def cpu(self):
|
|
return tep_record_cpu_get(self._record)
|
|
|
|
@cached_property
|
|
def name(self):
|
|
return event_format_name_get(self._format)
|
|
|
|
@cached_property
|
|
def pid(self):
|
|
return tep_data_pid(self._pevent, self._record)
|
|
|
|
@cached_property
|
|
def ts(self):
|
|
return tep_record_ts_get(self._record)
|
|
|
|
@cached_property
|
|
def type(self):
|
|
return tep_data_type(self._pevent, self._record)
|
|
|
|
def num_field(self, name):
|
|
f = tep_find_any_field(self._format, name)
|
|
if f is None:
|
|
return None
|
|
ret, val = tep_read_number_field(f, tep_record_data_get(self._record))
|
|
if ret:
|
|
return None
|
|
return val
|
|
|
|
def str_field(self, name):
|
|
f = tep_find_any_field(self._format, name)
|
|
if f is None:
|
|
return None
|
|
return py_field_get_str(f, self._record)
|
|
|
|
def stack_field(self, long_size):
|
|
return py_field_get_stack(self._pevent, self._record, self._format,
|
|
long_size)
|
|
|
|
class TraceSeq(object):
|
|
def __init__(self, trace_seq):
|
|
self._trace_seq = trace_seq
|
|
|
|
def puts(self, s):
|
|
return trace_seq_puts(self._trace_seq, s)
|
|
|
|
class FieldError(Exception):
|
|
pass
|
|
|
|
class Field(object):
|
|
def __init__(self, record, field):
|
|
self._record = record
|
|
self._field = field
|
|
|
|
@cached_property
|
|
def data(self):
|
|
return py_field_get_data(self._field, self._record)
|
|
|
|
def __long__(self):
|
|
ret, val = tep_read_number_field(self._field,
|
|
tep_record_data_get(self._record))
|
|
if ret:
|
|
raise FieldError("Not a number field")
|
|
return val
|
|
__int__ = __long__
|
|
|
|
def __str__(self):
|
|
return py_field_get_str(self._field, self._record)
|
|
|
|
class PEvent(object):
|
|
def __init__(self, pevent):
|
|
self._pevent = pevent
|
|
|
|
def _handler(self, cb, s, record, event_fmt):
|
|
return cb(TraceSeq(s), Event(self._pevent, record, event_fmt))
|
|
|
|
def register_event_handler(self, subsys, event_name, callback):
|
|
l = lambda s, r, e: self._handler(callback, s, r, e)
|
|
|
|
py_pevent_register_event_handler(
|
|
self._pevent, -1, subsys, event_name, l)
|
|
|
|
@cached_property
|
|
def file_endian(self):
|
|
if tep_is_file_bigendian(self._pevent):
|
|
return '>'
|
|
return '<'
|
|
|
|
|
|
class FileFormatError(Exception):
|
|
pass
|
|
|
|
class Trace(object):
|
|
"""
|
|
Trace object represents the trace file it is created with.
|
|
|
|
The Trace object aggregates the tracecmd structures and functions that are
|
|
used to manage the trace and extract events from it.
|
|
"""
|
|
def __init__(self, filename):
|
|
self._handle = tracecmd_alloc(filename)
|
|
|
|
if tracecmd_read_headers(self._handle):
|
|
raise FileFormatError("Invalid headers")
|
|
|
|
if tracecmd_init_data(self._handle):
|
|
raise FileFormatError("Failed to init data")
|
|
|
|
self._pevent = tracecmd_get_pevent(self._handle)
|
|
|
|
@cached_property
|
|
def cpus(self):
|
|
return tracecmd_cpus(self._handle)
|
|
|
|
@cached_property
|
|
def long_size(self):
|
|
return tracecmd_long_size(self._handle)
|
|
|
|
def read_event(self, cpu):
|
|
rec = tracecmd_read_data(self._handle, cpu)
|
|
if rec:
|
|
type = tep_data_type(self._pevent, rec)
|
|
format = tep_find_event(self._pevent, type)
|
|
# rec ownership goes over to Event instance
|
|
return Event(self._pevent, rec, format)
|
|
return None
|
|
|
|
def read_event_at(self, offset):
|
|
res = tracecmd_read_at(self._handle, offset)
|
|
# SWIG only returns the CPU if the record is None for some reason
|
|
if isinstance(res, int):
|
|
return None
|
|
rec, cpu = res
|
|
type = tep_data_type(self._pevent, rec)
|
|
format = tep_find_event(self._pevent, type)
|
|
# rec ownership goes over to Event instance
|
|
return Event(self._pevent, rec, format)
|
|
|
|
def read_next_event(self):
|
|
res = tracecmd_read_next_data(self._handle)
|
|
if isinstance(res, int):
|
|
return None
|
|
rec, cpu = res
|
|
type = tep_data_type(self._pevent, rec)
|
|
format = tep_find_event(self._pevent, type)
|
|
return Event(self._pevent, rec, format)
|
|
|
|
def peek_event(self, cpu):
|
|
rec = tracecmd_peek_data_ref(self._handle, cpu)
|
|
if rec is None:
|
|
return None
|
|
type = tep_data_type(self._pevent, rec)
|
|
format = tep_find_event(self._pevent, type)
|
|
# rec ownership goes over to Event instance
|
|
return Event(self._pevent, rec, format)
|
|
|
|
|
|
# Basic builtin test, execute module directly
|
|
if __name__ == "__main__":
|
|
t = Trace("trace.dat")
|
|
print("Trace contains data for %d cpus" % (t.cpus))
|
|
|
|
for cpu in range(0, t.cpus):
|
|
print("CPU %d" % (cpu))
|
|
ev = t.read_event(cpu)
|
|
while ev:
|
|
print("\t%s" % (ev))
|
|
ev = t.read_event(cpu)
|
|
|
|
|
|
|