blob: cef81b0707f0f4ff2236d07aed349af14f53cee3 [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
Mads Ynddal3470fef2023-09-26 12:34:34 +020015import warnings
Daniel P. Berranged1b97bc2016-10-04 14:35:56 +010016from tracetool import read_events, Event
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053017from tracetool.backend.simple import is_string
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010018
Mads Ynddal3470fef2023-09-26 12:34:34 +020019__all__ = ['Analyzer', 'Analyzer2', 'process', 'run']
Mads Ynddal2c109f22023-09-26 12:34:23 +020020
Mads Ynddal8405ec62023-09-26 12:34:24 +020021# This is the binary format that the QEMU "simple" trace backend
22# emits. There is no specification documentation because the format is
23# not guaranteed to be stable. Trace files must be parsed with the
24# same trace-events-all file and the same simpletrace.py file that
25# QEMU was built with.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010026header_event_id = 0xffffffffffffffff
27header_magic = 0xf2b177cb0aa429b4
Stefan Hajnoczi0b5538c2011-02-26 18:38:39 +000028dropped_event_id = 0xfffffffffffffffe
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010029
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010030record_type_mapping = 0
31record_type_event = 1
32
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053033log_header_fmt = '=QQQ'
34rec_header_fmt = '=QQII'
Mads Ynddal6f536412023-09-26 12:34:31 +020035rec_header_fmt_len = struct.calcsize(rec_header_fmt)
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010036
Mads Ynddal1990fb92023-09-26 12:34:29 +020037class SimpleException(Exception):
38 pass
39
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053040def read_header(fobj, hfmt):
41 '''Read a trace record header'''
42 hlen = struct.calcsize(hfmt)
43 hdr = fobj.read(hlen)
44 if len(hdr) != hlen:
Mads Ynddal1990fb92023-09-26 12:34:29 +020045 raise SimpleException('Error reading header. Wrong filetype provided?')
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053046 return struct.unpack(hfmt, hdr)
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010047
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010048def get_mapping(fobj):
49 (event_id, ) = struct.unpack('=Q', fobj.read(8))
50 (len, ) = struct.unpack('=L', fobj.read(4))
Eduardo Habkost749c1d82018-06-19 16:45:49 -030051 name = fobj.read(len).decode()
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053052
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010053 return (event_id, name)
54
Mads Ynddal6f536412023-09-26 12:34:31 +020055def read_record(fobj):
56 """Deserialize a trace record from a file into a tuple (event_num, timestamp, pid, args)."""
57 event_id, timestamp_ns, record_length, record_pid = read_header(fobj, rec_header_fmt)
58 args_payload = fobj.read(record_length - rec_header_fmt_len)
59 return (event_id, timestamp_ns, record_pid, args_payload)
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053060
Stefan Hajnoczi15327c32014-06-22 21:46:06 +080061def read_trace_header(fobj):
62 """Read and verify trace file header"""
Mads Ynddald1f92592023-09-26 12:34:28 +020063 _header_event_id, _header_magic, log_version = read_header(fobj, log_header_fmt)
64 if _header_event_id != header_event_id:
65 raise ValueError(f'Not a valid trace file, header id {_header_event_id} != {header_event_id}')
66 if _header_magic != header_magic:
67 raise ValueError(f'Not a valid trace file, header magic {_header_magic} != {header_magic}')
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053068
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010069 if log_version not in [0, 2, 3, 4]:
Mads Ynddald1f92592023-09-26 12:34:28 +020070 raise ValueError(f'Unknown version {log_version} of tracelog format!')
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010071 if log_version != 4:
Mads Ynddald1f92592023-09-26 12:34:28 +020072 raise ValueError(f'Log format {log_version} not supported with this QEMU release!')
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010073
Mads Ynddal6f536412023-09-26 12:34:31 +020074def read_trace_records(events, fobj, read_header):
75 """Deserialize trace records from a file, yielding record tuples (event, event_num, timestamp, pid, arg1, ..., arg6).
Stefan Hajnoczi840d8352017-08-15 09:44:30 +010076
77 Args:
Mads Ynddal3b71b612023-09-26 12:34:26 +020078 event_mapping (str -> Event): events dict, indexed by name
Stefan Hajnoczi840d8352017-08-15 09:44:30 +010079 fobj (file): input file
Mads Ynddal6f536412023-09-26 12:34:31 +020080 read_header (bool): whether headers were read from fobj
Stefan Hajnoczi840d8352017-08-15 09:44:30 +010081
82 """
Mads Ynddal6f536412023-09-26 12:34:31 +020083 frameinfo = inspect.getframeinfo(inspect.currentframe())
84 dropped_event = Event.build("Dropped_Event(uint64_t num_events_dropped)",
85 frameinfo.lineno + 1, frameinfo.filename)
86
87 event_mapping = {e.name: e for e in events}
88 event_mapping["dropped"] = dropped_event
89 event_id_to_name = {dropped_event_id: "dropped"}
90
91 # If there is no header assume event ID mapping matches events list
92 if not read_header:
93 for event_id, event in enumerate(events):
94 event_id_to_name[event_id] = event.name
95
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010096 while True:
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010097 t = fobj.read(8)
98 if len(t) == 0:
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010099 break
100
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100101 (rectype, ) = struct.unpack('=Q', t)
102 if rectype == record_type_mapping:
Mads Ynddal6f536412023-09-26 12:34:31 +0200103 event_id, event_name = get_mapping(fobj)
104 event_id_to_name[event_id] = event_name
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100105 else:
Mads Ynddal6f536412023-09-26 12:34:31 +0200106 event_id, timestamp_ns, pid, args_payload = read_record(fobj)
107 event_name = event_id_to_name[event_id]
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100108
Mads Ynddal6f536412023-09-26 12:34:31 +0200109 try:
110 event = event_mapping[event_name]
111 except KeyError as e:
112 raise SimpleException(
113 f'{e} event is logged but is not declared in the trace events'
114 'file, try using trace-events-all instead.'
115 )
116
117 offset = 0
118 args = []
119 for type, _ in event.args:
120 if is_string(type):
121 (length,) = struct.unpack_from('=L', args_payload, offset=offset)
122 offset += 4
123 s = args_payload[offset:offset+length]
124 offset += length
125 args.append(s)
126 else:
127 (value,) = struct.unpack_from('=Q', args_payload, offset=offset)
128 offset += 8
129 args.append(value)
130
131 yield (event_mapping[event_name], event_name, timestamp_ns, pid) + tuple(args)
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100132
Mads Ynddal87617b92023-09-26 12:34:30 +0200133class Analyzer:
Mads Ynddal3470fef2023-09-26 12:34:34 +0200134 """[Deprecated. Refer to Analyzer2 instead.]
135
136 A trace file analyzer which processes trace records.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100137
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000138 An analyzer can be passed to run() or process(). The begin() method is
139 invoked, then each trace record is processed, and finally the end() method
Mads Ynddal87617b92023-09-26 12:34:30 +0200140 is invoked. When Analyzer is used as a context-manager (using the `with`
141 statement), begin() and end() are called automatically.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100142
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000143 If a method matching a trace event name exists, it is invoked to process
Stefan Hajnoczi659370f2017-04-11 10:56:54 +0100144 that trace record. Otherwise the catchall() method is invoked.
145
146 Example:
147 The following method handles the runstate_set(int new_state) trace event::
148
149 def runstate_set(self, new_state):
150 ...
151
152 The method can also take a timestamp argument before the trace event
153 arguments::
154
155 def runstate_set(self, timestamp, new_state):
156 ...
157
158 Timestamps have the uint64_t type and are in nanoseconds.
159
160 The pid can be included in addition to the timestamp and is useful when
161 dealing with traces from multiple processes::
162
163 def runstate_set(self, timestamp, pid, new_state):
164 ...
165 """
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100166
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000167 def begin(self):
168 """Called at the start of the trace."""
169 pass
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100170
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000171 def catchall(self, event, rec):
172 """Called if no specific method for processing a trace event has been found."""
173 pass
174
Mads Ynddald1f89c22023-09-26 12:34:33 +0200175 def _build_fn(self, event):
176 fn = getattr(self, event.name, None)
177 if fn is None:
178 # Return early to avoid costly call to inspect.getfullargspec
179 return self.catchall
180
181 event_argcount = len(event.args)
182 fn_argcount = len(inspect.getfullargspec(fn)[0]) - 1
183 if fn_argcount == event_argcount + 1:
184 # Include timestamp as first argument
185 return lambda _, rec: fn(*(rec[1:2] + rec[3:3 + event_argcount]))
186 elif fn_argcount == event_argcount + 2:
187 # Include timestamp and pid
188 return lambda _, rec: fn(*rec[1:3 + event_argcount])
189 else:
190 # Just arguments, no timestamp or pid
191 return lambda _, rec: fn(*rec[3:3 + event_argcount])
192
193 def _process_event(self, rec_args, *, event, event_id, timestamp_ns, pid, **kwargs):
Mads Ynddal3470fef2023-09-26 12:34:34 +0200194 warnings.warn(
195 "Use of deprecated Analyzer class. Refer to Analyzer2 instead.",
196 DeprecationWarning,
197 )
198
Mads Ynddald1f89c22023-09-26 12:34:33 +0200199 if not hasattr(self, '_fn_cache'):
200 # NOTE: Cannot depend on downstream subclasses to have
201 # super().__init__() because of legacy.
202 self._fn_cache = {}
203
204 rec = (event_id, timestamp_ns, pid, *rec_args)
205 if event_id not in self._fn_cache:
206 self._fn_cache[event_id] = self._build_fn(event)
207 self._fn_cache[event_id](event, rec)
208
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000209 def end(self):
210 """Called at the end of the trace."""
211 pass
212
Mads Ynddal87617b92023-09-26 12:34:30 +0200213 def __enter__(self):
214 self.begin()
215 return self
216
217 def __exit__(self, exc_type, exc_val, exc_tb):
218 if exc_type is None:
219 self.end()
220 return False
221
Mads Ynddal3470fef2023-09-26 12:34:34 +0200222class Analyzer2(Analyzer):
223 """A trace file analyzer which processes trace records.
224
225 An analyzer can be passed to run() or process(). The begin() method is
226 invoked, then each trace record is processed, and finally the end() method
227 is invoked. When Analyzer is used as a context-manager (using the `with`
228 statement), begin() and end() are called automatically.
229
230 If a method matching a trace event name exists, it is invoked to process
231 that trace record. Otherwise the catchall() method is invoked.
232
233 The methods are called with a set of keyword-arguments. These can be ignored
234 using `**kwargs` or defined like any keyword-argument.
235
236 The following keyword-arguments are available, but make sure to have an
237 **kwargs to allow for unmatched arguments in the future:
238 event: Event object of current trace
239 event_id: The id of the event in the current trace file
240 timestamp_ns: The timestamp in nanoseconds of the trace
241 pid: The process id recorded for the given trace
242
243 Example:
244 The following method handles the runstate_set(int new_state) trace event::
245
246 def runstate_set(self, new_state, **kwargs):
247 ...
248
249 The method can also explicitly take a timestamp keyword-argument with the
250 trace event arguments::
251
252 def runstate_set(self, new_state, *, timestamp_ns, **kwargs):
253 ...
254
255 Timestamps have the uint64_t type and are in nanoseconds.
256
257 The pid can be included in addition to the timestamp and is useful when
258 dealing with traces from multiple processes:
259
260 def runstate_set(self, new_state, *, timestamp_ns, pid, **kwargs):
261 ...
262 """
263
264 def catchall(self, *rec_args, event, timestamp_ns, pid, event_id, **kwargs):
265 """Called if no specific method for processing a trace event has been found."""
266 pass
267
268 def _process_event(self, rec_args, *, event, **kwargs):
269 fn = getattr(self, event.name, self.catchall)
270 fn(*rec_args, event=event, **kwargs)
271
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800272def process(events, log, analyzer, read_header=True):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200273 """Invoke an analyzer on each event in a log.
274 Args:
275 events (file-object or list or str): events list or file-like object or file path as str to read event data from
276 log (file-object or str): file-like object or file path as str to read log data from
277 analyzer (Analyzer): Instance of Analyzer to interpret the event data
278 read_header (bool, optional): Whether to read header data from the log data. Defaults to True.
279 """
280
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000281 if isinstance(events, str):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200282 with open(events, 'r') as f:
283 events_list = read_events(f, events)
284 elif isinstance(events, list):
285 # Treat as a list of events already produced by tracetool.read_events
286 events_list = events
287 else:
288 # Treat as an already opened file-object
289 events_list = read_events(events, events.name)
290
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000291 if isinstance(log, str):
Mads Ynddalb78234e2023-09-26 12:34:32 +0200292 with open(log, 'rb') as log_fobj:
293 _process(events_list, log_fobj, analyzer, read_header)
294 else:
295 # Treat `log` as an already opened file-object. We will not close it,
296 # as we do not own it.
297 _process(events_list, log, analyzer, read_header)
298
299def _process(events, log_fobj, analyzer, read_header=True):
300 """Internal function for processing
301
302 Args:
303 events (list): list of events already produced by tracetool.read_events
304 log_fobj (file): file-object to read log data from
305 analyzer (Analyzer): the Analyzer to interpret the event data
306 read_header (bool, optional): Whether to read header data from the log data. Defaults to True.
307 """
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000308
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800309 if read_header:
Mads Ynddalb78234e2023-09-26 12:34:32 +0200310 read_trace_header(log_fobj)
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800311
Mads Ynddal87617b92023-09-26 12:34:30 +0200312 with analyzer:
Mads Ynddalb78234e2023-09-26 12:34:32 +0200313 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 +0200314 analyzer._process_event(
315 rec_args,
316 event=event,
317 event_id=event_id,
318 timestamp_ns=timestamp_ns,
319 pid=record_pid,
320 )
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000321
322def run(analyzer):
323 """Execute an analyzer on a trace file given on the command-line.
324
325 This function is useful as a driver for simple analysis scripts. More
326 advanced scripts will want to call process() instead."""
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000327
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200328 try:
329 # NOTE: See built-in `argparse` module for a more robust cli interface
330 *no_header, trace_event_path, trace_file_path = sys.argv[1:]
331 assert no_header == [] or no_header == ['--no-header'], 'Invalid no-header argument'
332 except (AssertionError, ValueError):
Mads Ynddal1990fb92023-09-26 12:34:29 +0200333 raise SimpleException(f'usage: {sys.argv[0]} [--no-header] <trace-events> <trace-file>\n')
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000334
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200335 with open(trace_event_path, 'r') as events_fobj, open(trace_file_path, 'rb') as log_fobj:
336 process(events_fobj, log_fobj, analyzer, read_header=not no_header)
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000337
338if __name__ == '__main__':
Mads Ynddal3470fef2023-09-26 12:34:34 +0200339 class Formatter2(Analyzer2):
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000340 def __init__(self):
Mads Ynddal3470fef2023-09-26 12:34:34 +0200341 self.last_timestamp_ns = None
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000342
Mads Ynddal3470fef2023-09-26 12:34:34 +0200343 def catchall(self, *rec_args, event, timestamp_ns, pid, event_id):
344 if self.last_timestamp_ns is None:
345 self.last_timestamp_ns = timestamp_ns
346 delta_ns = timestamp_ns - self.last_timestamp_ns
347 self.last_timestamp_ns = timestamp_ns
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000348
Mads Ynddal3470fef2023-09-26 12:34:34 +0200349 fields = [
350 f'{name}={r}' if is_string(type) else f'{name}=0x{r:x}'
351 for r, (type, name) in zip(rec_args, event.args)
352 ]
353 print(f'{event.name} {delta_ns / 1000:0.3f} {pid=} ' + ' '.join(fields))
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000354
Mads Ynddal1990fb92023-09-26 12:34:29 +0200355 try:
Mads Ynddal3470fef2023-09-26 12:34:34 +0200356 run(Formatter2())
Mads Ynddal1990fb92023-09-26 12:34:29 +0200357 except SimpleException as e:
358 sys.stderr.write(str(e) + "\n")
359 sys.exit(1)