blob: 4136d006001f76e76802ea3e567f46d32dd9c1d1 [file] [log] [blame]
Philippe Mathieu-Daudé3d004a32020-01-30 17:32:25 +01001#!/usr/bin/env python3
Stefan Hajnoczi26f72272010-05-22 19:24:51 +01002#
3# Pretty-printer for simple trace backend binary trace files
4#
5# Copyright IBM, Corp. 2010
6#
7# This work is licensed under the terms of the GNU GPL, version 2. See
8# the COPYING file in the top-level directory.
9#
Stefano Garzarellad0fb9652021-05-17 17:16:58 +020010# For help see docs/devel/tracing.rst
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010011
Mads Ynddalf7bd4f02023-09-26 12:34:25 +020012import sys
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010013import struct
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000014import inspect
Daniel P. Berranged1b97bc2016-10-04 14:35:56 +010015from tracetool import read_events, Event
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053016from tracetool.backend.simple import is_string
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010017
Mads Ynddal2c109f22023-09-26 12:34:23 +020018__all__ = ['Analyzer', 'process', 'run']
19
Mads Ynddal8405ec62023-09-26 12:34:24 +020020# This is the binary format that the QEMU "simple" trace backend
21# emits. There is no specification documentation because the format is
22# not guaranteed to be stable. Trace files must be parsed with the
23# same trace-events-all file and the same simpletrace.py file that
24# QEMU was built with.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010025header_event_id = 0xffffffffffffffff
26header_magic = 0xf2b177cb0aa429b4
Stefan Hajnoczi0b5538c2011-02-26 18:38:39 +000027dropped_event_id = 0xfffffffffffffffe
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010028
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010029record_type_mapping = 0
30record_type_event = 1
31
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053032log_header_fmt = '=QQQ'
33rec_header_fmt = '=QQII'
Mads Ynddal6f536412023-09-26 12:34:31 +020034rec_header_fmt_len = struct.calcsize(rec_header_fmt)
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010035
Mads Ynddal1990fb92023-09-26 12:34:29 +020036class SimpleException(Exception):
37 pass
38
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053039def read_header(fobj, hfmt):
40 '''Read a trace record header'''
41 hlen = struct.calcsize(hfmt)
42 hdr = fobj.read(hlen)
43 if len(hdr) != hlen:
Mads Ynddal1990fb92023-09-26 12:34:29 +020044 raise SimpleException('Error reading header. Wrong filetype provided?')
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053045 return struct.unpack(hfmt, hdr)
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010046
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010047def get_mapping(fobj):
48 (event_id, ) = struct.unpack('=Q', fobj.read(8))
49 (len, ) = struct.unpack('=L', fobj.read(4))
Eduardo Habkost749c1d82018-06-19 16:45:49 -030050 name = fobj.read(len).decode()
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053051
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010052 return (event_id, name)
53
Mads Ynddal6f536412023-09-26 12:34:31 +020054def read_record(fobj):
55 """Deserialize a trace record from a file into a tuple (event_num, timestamp, pid, args)."""
56 event_id, timestamp_ns, record_length, record_pid = read_header(fobj, rec_header_fmt)
57 args_payload = fobj.read(record_length - rec_header_fmt_len)
58 return (event_id, timestamp_ns, record_pid, args_payload)
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053059
Stefan Hajnoczi15327c32014-06-22 21:46:06 +080060def read_trace_header(fobj):
61 """Read and verify trace file header"""
Mads Ynddald1f92592023-09-26 12:34:28 +020062 _header_event_id, _header_magic, log_version = read_header(fobj, log_header_fmt)
63 if _header_event_id != header_event_id:
64 raise ValueError(f'Not a valid trace file, header id {_header_event_id} != {header_event_id}')
65 if _header_magic != header_magic:
66 raise ValueError(f'Not a valid trace file, header magic {_header_magic} != {header_magic}')
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053067
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010068 if log_version not in [0, 2, 3, 4]:
Mads Ynddald1f92592023-09-26 12:34:28 +020069 raise ValueError(f'Unknown version {log_version} of tracelog format!')
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010070 if log_version != 4:
Mads Ynddald1f92592023-09-26 12:34:28 +020071 raise ValueError(f'Log format {log_version} not supported with this QEMU release!')
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010072
Mads Ynddal6f536412023-09-26 12:34:31 +020073def read_trace_records(events, fobj, read_header):
74 """Deserialize trace records from a file, yielding record tuples (event, event_num, timestamp, pid, arg1, ..., arg6).
Stefan Hajnoczi840d8352017-08-15 09:44:30 +010075
76 Args:
Mads Ynddal3b71b612023-09-26 12:34:26 +020077 event_mapping (str -> Event): events dict, indexed by name
Stefan Hajnoczi840d8352017-08-15 09:44:30 +010078 fobj (file): input file
Mads Ynddal6f536412023-09-26 12:34:31 +020079 read_header (bool): whether headers were read from fobj
Stefan Hajnoczi840d8352017-08-15 09:44:30 +010080
81 """
Mads Ynddal6f536412023-09-26 12:34:31 +020082 frameinfo = inspect.getframeinfo(inspect.currentframe())
83 dropped_event = Event.build("Dropped_Event(uint64_t num_events_dropped)",
84 frameinfo.lineno + 1, frameinfo.filename)
85
86 event_mapping = {e.name: e for e in events}
87 event_mapping["dropped"] = dropped_event
88 event_id_to_name = {dropped_event_id: "dropped"}
89
90 # If there is no header assume event ID mapping matches events list
91 if not read_header:
92 for event_id, event in enumerate(events):
93 event_id_to_name[event_id] = event.name
94
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010095 while True:
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010096 t = fobj.read(8)
97 if len(t) == 0:
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010098 break
99
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100100 (rectype, ) = struct.unpack('=Q', t)
101 if rectype == record_type_mapping:
Mads Ynddal6f536412023-09-26 12:34:31 +0200102 event_id, event_name = get_mapping(fobj)
103 event_id_to_name[event_id] = event_name
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100104 else:
Mads Ynddal6f536412023-09-26 12:34:31 +0200105 event_id, timestamp_ns, pid, args_payload = read_record(fobj)
106 event_name = event_id_to_name[event_id]
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100107
Mads Ynddal6f536412023-09-26 12:34:31 +0200108 try:
109 event = event_mapping[event_name]
110 except KeyError as e:
111 raise SimpleException(
112 f'{e} event is logged but is not declared in the trace events'
113 'file, try using trace-events-all instead.'
114 )
115
116 offset = 0
117 args = []
118 for type, _ in event.args:
119 if is_string(type):
120 (length,) = struct.unpack_from('=L', args_payload, offset=offset)
121 offset += 4
122 s = args_payload[offset:offset+length]
123 offset += length
124 args.append(s)
125 else:
126 (value,) = struct.unpack_from('=Q', args_payload, offset=offset)
127 offset += 8
128 args.append(value)
129
130 yield (event_mapping[event_name], event_name, timestamp_ns, pid) + tuple(args)
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100131
Mads Ynddal87617b92023-09-26 12:34:30 +0200132class Analyzer:
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000133 """A trace file analyzer which processes trace records.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100134
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000135 An analyzer can be passed to run() or process(). The begin() method is
136 invoked, then each trace record is processed, and finally the end() method
Mads Ynddal87617b92023-09-26 12:34:30 +0200137 is invoked. When Analyzer is used as a context-manager (using the `with`
138 statement), begin() and end() are called automatically.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100139
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000140 If a method matching a trace event name exists, it is invoked to process
Stefan Hajnoczi659370f2017-04-11 10:56:54 +0100141 that trace record. Otherwise the catchall() method is invoked.
142
143 Example:
144 The following method handles the runstate_set(int new_state) trace event::
145
146 def runstate_set(self, new_state):
147 ...
148
149 The method can also take a timestamp argument before the trace event
150 arguments::
151
152 def runstate_set(self, timestamp, new_state):
153 ...
154
155 Timestamps have the uint64_t type and are in nanoseconds.
156
157 The pid can be included in addition to the timestamp and is useful when
158 dealing with traces from multiple processes::
159
160 def runstate_set(self, timestamp, pid, new_state):
161 ...
162 """
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100163
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000164 def begin(self):
165 """Called at the start of the trace."""
166 pass
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100167
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000168 def catchall(self, event, rec):
169 """Called if no specific method for processing a trace event has been found."""
170 pass
171
Mads Ynddald1f89c22023-09-26 12:34:33 +0200172 def _build_fn(self, event):
173 fn = getattr(self, event.name, None)
174 if fn is None:
175 # Return early to avoid costly call to inspect.getfullargspec
176 return self.catchall
177
178 event_argcount = len(event.args)
179 fn_argcount = len(inspect.getfullargspec(fn)[0]) - 1
180 if fn_argcount == event_argcount + 1:
181 # Include timestamp as first argument
182 return lambda _, rec: fn(*(rec[1:2] + rec[3:3 + event_argcount]))
183 elif fn_argcount == event_argcount + 2:
184 # Include timestamp and pid
185 return lambda _, rec: fn(*rec[1:3 + event_argcount])
186 else:
187 # Just arguments, no timestamp or pid
188 return lambda _, rec: fn(*rec[3:3 + event_argcount])
189
190 def _process_event(self, rec_args, *, event, event_id, timestamp_ns, pid, **kwargs):
191 if not hasattr(self, '_fn_cache'):
192 # NOTE: Cannot depend on downstream subclasses to have
193 # super().__init__() because of legacy.
194 self._fn_cache = {}
195
196 rec = (event_id, timestamp_ns, pid, *rec_args)
197 if event_id not in self._fn_cache:
198 self._fn_cache[event_id] = self._build_fn(event)
199 self._fn_cache[event_id](event, rec)
200
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000201 def end(self):
202 """Called at the end of the trace."""
203 pass
204
Mads Ynddal87617b92023-09-26 12:34:30 +0200205 def __enter__(self):
206 self.begin()
207 return self
208
209 def __exit__(self, exc_type, exc_val, exc_tb):
210 if exc_type is None:
211 self.end()
212 return False
213
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800214def process(events, log, analyzer, read_header=True):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200215 """Invoke an analyzer on each event in a log.
216 Args:
217 events (file-object or list or str): events list or file-like object or file path as str to read event data from
218 log (file-object or str): file-like object or file path as str to read log data from
219 analyzer (Analyzer): Instance of Analyzer to interpret the event data
220 read_header (bool, optional): Whether to read header data from the log data. Defaults to True.
221 """
222
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000223 if isinstance(events, str):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200224 with open(events, 'r') as f:
225 events_list = read_events(f, events)
226 elif isinstance(events, list):
227 # Treat as a list of events already produced by tracetool.read_events
228 events_list = events
229 else:
230 # Treat as an already opened file-object
231 events_list = read_events(events, events.name)
232
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000233 if isinstance(log, str):
Mads Ynddalb78234e2023-09-26 12:34:32 +0200234 with open(log, 'rb') as log_fobj:
235 _process(events_list, log_fobj, analyzer, read_header)
236 else:
237 # Treat `log` as an already opened file-object. We will not close it,
238 # as we do not own it.
239 _process(events_list, log, analyzer, read_header)
240
241def _process(events, log_fobj, analyzer, read_header=True):
242 """Internal function for processing
243
244 Args:
245 events (list): list of events already produced by tracetool.read_events
246 log_fobj (file): file-object to read log data from
247 analyzer (Analyzer): the Analyzer to interpret the event data
248 read_header (bool, optional): Whether to read header data from the log data. Defaults to True.
249 """
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000250
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800251 if read_header:
Mads Ynddalb78234e2023-09-26 12:34:32 +0200252 read_trace_header(log_fobj)
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800253
Mads Ynddal87617b92023-09-26 12:34:30 +0200254 with analyzer:
Mads Ynddalb78234e2023-09-26 12:34:32 +0200255 for event, event_id, timestamp_ns, record_pid, *rec_args in read_trace_records(events, log_fobj, read_header):
Mads Ynddald1f89c22023-09-26 12:34:33 +0200256 analyzer._process_event(
257 rec_args,
258 event=event,
259 event_id=event_id,
260 timestamp_ns=timestamp_ns,
261 pid=record_pid,
262 )
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000263
264def run(analyzer):
265 """Execute an analyzer on a trace file given on the command-line.
266
267 This function is useful as a driver for simple analysis scripts. More
268 advanced scripts will want to call process() instead."""
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000269
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200270 try:
271 # NOTE: See built-in `argparse` module for a more robust cli interface
272 *no_header, trace_event_path, trace_file_path = sys.argv[1:]
273 assert no_header == [] or no_header == ['--no-header'], 'Invalid no-header argument'
274 except (AssertionError, ValueError):
Mads Ynddal1990fb92023-09-26 12:34:29 +0200275 raise SimpleException(f'usage: {sys.argv[0]} [--no-header] <trace-events> <trace-file>\n')
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000276
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200277 with open(trace_event_path, 'r') as events_fobj, open(trace_file_path, 'rb') as log_fobj:
278 process(events_fobj, log_fobj, analyzer, read_header=not no_header)
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000279
280if __name__ == '__main__':
281 class Formatter(Analyzer):
282 def __init__(self):
283 self.last_timestamp = None
284
285 def catchall(self, event, rec):
286 timestamp = rec[1]
287 if self.last_timestamp is None:
288 self.last_timestamp = timestamp
289 delta_ns = timestamp - self.last_timestamp
290 self.last_timestamp = timestamp
291
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200292 fields = [event.name, '%0.3f' % (delta_ns / 1000.0),
293 'pid=%d' % rec[2]]
294 i = 3
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530295 for type, name in event.args:
296 if is_string(type):
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200297 fields.append('%s=%s' % (name, rec[i]))
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530298 else:
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200299 fields.append('%s=0x%x' % (name, rec[i]))
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530300 i += 1
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300301 print(' '.join(fields))
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000302
Mads Ynddal1990fb92023-09-26 12:34:29 +0200303 try:
304 run(Formatter())
305 except SimpleException as e:
306 sys.stderr.write(str(e) + "\n")
307 sys.exit(1)