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