• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (C) International Business Machines  Corp., 2009
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17#
18# 2009-Dec-17:	Initial version by Darren Hart <dvhltc@us.ibm.com>
19#
20
21from functools import cached_property
22from collections.abc import Mapping
23from itertools import chain
24from ctracecmd import *
25
26"""
27Python interface to the tracecmd library for parsing ftrace traces
28
29Python tracecmd applications should be written to this interface. It will be
30updated as the tracecmd C API changes and try to minimze the impact to python
31applications. The ctracecmd Python module is automatically generated using SWIG
32and it is recommended applications not use it directly.
33
34TODO: consider a complete class hierarchy of ftrace events...
35"""
36
37class Event(Mapping):
38    """
39    This class can be used to access event data
40    according to an event's record and format.
41    """
42    def __init__(self, pevent, record, format):
43        self._pevent = pevent
44        self._record = record
45        self._format = format
46
47    def __str__(self):
48        return "%d.%09d CPU%d %s: pid=%d comm=%s type=%d" % \
49               (self.ts/1000000000, self.ts%1000000000, self.cpu, self.name,
50                self.num_field("common_pid"), self.comm, self.type)
51
52    def __del__(self):
53        tracecmd_free_record(self._record)
54
55    def __getitem__(self, n):
56        if n.startswith('common_'):
57            f = tep_find_common_field(self._format, n)
58        else:
59            f = tep_find_field(self._format, n)
60        if f is None:
61            raise KeyError("no field '%s'" % n)
62        return Field(self._record, f)
63
64    def __iter__(self):
65        yield from chain(self.common_keys, self.keys)
66
67    def __len__(self):
68        return len(self.common_keys) + len(self.keys)
69
70    @cached_property
71    def common_keys(self):
72        return py_format_get_keys(self._format, True)
73
74    @cached_property
75    def keys(self):
76        return py_format_get_keys(self._format, False)
77
78    @cached_property
79    def comm(self):
80        return tep_data_comm_from_pid(self._pevent, self.pid)
81
82    @cached_property
83    def cpu(self):
84        return tep_record_cpu_get(self._record)
85
86    @cached_property
87    def name(self):
88        return tep_event_name_get(self._format)
89
90    @cached_property
91    def pid(self):
92        return tep_data_pid(self._pevent, self._record)
93
94    @cached_property
95    def ts(self):
96        return tep_record_ts_get(self._record)
97
98    @cached_property
99    def type(self):
100        return tep_data_type(self._pevent, self._record)
101
102    def num_field(self, name):
103        f = tep_find_any_field(self._format, name)
104        if f is None:
105            return None
106        ret, val = tep_read_number_field(f, tep_record_data_get(self._record))
107        if ret:
108            return None
109        return val
110
111    def str_field(self, name):
112        f = tep_find_any_field(self._format, name)
113        if f is None:
114            return None
115        return py_field_get_str(f, self._record)
116
117    def stack_field(self, long_size):
118        return py_field_get_stack(self._pevent, self._record, self._format,
119                                  long_size)
120
121class TraceSeq(object):
122    def __init__(self, trace_seq):
123        self._trace_seq = trace_seq
124
125    def puts(self, s):
126        return trace_seq_puts(self._trace_seq, s)
127
128class FieldError(Exception):
129    pass
130
131class Field(object):
132    def __init__(self, record, field):
133        self._record = record
134        self._field = field
135
136    @cached_property
137    def data(self):
138        return py_field_get_data(self._field, self._record)
139
140    def __long__(self):
141        ret, val =  tep_read_number_field(self._field,
142                                          tep_record_data_get(self._record))
143        if ret:
144            raise FieldError("Not a number field")
145        return val
146    __int__ = __long__
147
148    def __str__(self):
149        return py_field_get_str(self._field, self._record)
150
151class PEvent(object):
152    def __init__(self, pevent):
153        self._pevent = pevent
154
155    def _handler(self, cb, s, record, event_fmt):
156        return cb(TraceSeq(s), Event(self._pevent, record, event_fmt))
157
158    def register_event_handler(self, subsys, event_name, callback):
159        l = lambda s, r, e: self._handler(callback, s, r, e)
160
161        py_pevent_register_event_handler(
162                  self._pevent, -1, subsys, event_name, l)
163
164    @cached_property
165    def file_endian(self):
166        if tep_is_file_bigendian(self._pevent):
167            return '>'
168        return '<'
169
170
171class FileFormatError(Exception):
172    pass
173
174class Trace(object):
175    """
176    Trace object represents the trace file it is created with.
177
178    The Trace object aggregates the tracecmd structures and functions that are
179    used to manage the trace and extract events from it.
180    """
181    def __init__(self, filename):
182        self._handle = tracecmd_open(filename, 0)
183        self._pevent = tracecmd_get_tep(self._handle)
184
185    @cached_property
186    def cpus(self):
187        return tracecmd_cpus(self._handle)
188
189    @cached_property
190    def long_size(self):
191        return tracecmd_long_size(self._handle)
192
193    def read_event(self, cpu):
194        rec = tracecmd_read_data(self._handle, cpu)
195        if rec:
196            type = tep_data_type(self._pevent, rec)
197            format = tep_find_event(self._pevent, type)
198            # rec ownership goes over to Event instance
199            return Event(self._pevent, rec, format)
200        return None
201
202    def read_event_at(self, offset):
203        res = tracecmd_read_at(self._handle, offset)
204        # SWIG only returns the CPU if the record is None for some reason
205        if isinstance(res, int):
206            return None
207        rec, cpu = res
208        type = tep_data_type(self._pevent, rec)
209        format = tep_find_event(self._pevent, type)
210        # rec ownership goes over to Event instance
211        return Event(self._pevent, rec, format)
212
213    def read_next_event(self):
214        res = tracecmd_read_next_data(self._handle)
215        if isinstance(res, int):
216            return None
217        rec, cpu = res
218        type = tep_data_type(self._pevent, rec)
219        format = tep_find_event(self._pevent, type)
220        return Event(self._pevent, rec, format)
221
222    def peek_event(self, cpu):
223        rec = tracecmd_peek_data_ref(self._handle, cpu)
224        if rec is None:
225            return None
226        type = tep_data_type(self._pevent, rec)
227        format = tep_find_event(self._pevent, type)
228        # rec ownership goes over to Event instance
229        return Event(self._pevent, rec, format)
230
231
232# Basic builtin test, execute module directly
233if __name__ == "__main__":
234    t = Trace("trace.dat")
235    print(f"Trace contains data for {t.cpus} cpus, long has {t.long_size} bytes")
236
237    print("Peek the first event on CPU0")
238    print("\t%s" % (t.peek_event(0)))
239
240    print("Events by CPUs")
241    for cpu in range(0, t.cpus):
242        print("CPU %d" % (cpu))
243        ev = t.read_event(cpu)
244        while ev:
245            print("\t%s" % (ev))
246            ev = t.read_event(cpu)
247
248    t = Trace("trace.dat")
249
250    print("Events by time")
251    ev = t.read_next_event()
252    while ev:
253        print("\t%s" % (ev))
254        ev = t.read_next_event()
255