blob: 6969fdd59a50dd9639c94bf0a6fbfe77439ff201 [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
172 def end(self):
173 """Called at the end of the trace."""
174 pass
175
Mads Ynddal87617b92023-09-26 12:34:30 +0200176 def __enter__(self):
177 self.begin()
178 return self
179
180 def __exit__(self, exc_type, exc_val, exc_tb):
181 if exc_type is None:
182 self.end()
183 return False
184
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800185def process(events, log, analyzer, read_header=True):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200186 """Invoke an analyzer on each event in a log.
187 Args:
188 events (file-object or list or str): events list or file-like object or file path as str to read event data from
189 log (file-object or str): file-like object or file path as str to read log data from
190 analyzer (Analyzer): Instance of Analyzer to interpret the event data
191 read_header (bool, optional): Whether to read header data from the log data. Defaults to True.
192 """
193
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000194 if isinstance(events, str):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200195 with open(events, 'r') as f:
196 events_list = read_events(f, events)
197 elif isinstance(events, list):
198 # Treat as a list of events already produced by tracetool.read_events
199 events_list = events
200 else:
201 # Treat as an already opened file-object
202 events_list = read_events(events, events.name)
203
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000204 if isinstance(log, str):
Mads Ynddalb78234e2023-09-26 12:34:32 +0200205 with open(log, 'rb') as log_fobj:
206 _process(events_list, log_fobj, analyzer, read_header)
207 else:
208 # Treat `log` as an already opened file-object. We will not close it,
209 # as we do not own it.
210 _process(events_list, log, analyzer, read_header)
211
212def _process(events, log_fobj, analyzer, read_header=True):
213 """Internal function for processing
214
215 Args:
216 events (list): list of events already produced by tracetool.read_events
217 log_fobj (file): file-object to read log data from
218 analyzer (Analyzer): the Analyzer to interpret the event data
219 read_header (bool, optional): Whether to read header data from the log data. Defaults to True.
220 """
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000221
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800222 if read_header:
Mads Ynddalb78234e2023-09-26 12:34:32 +0200223 read_trace_header(log_fobj)
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800224
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000225 def build_fn(analyzer, event):
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530226 if isinstance(event, str):
227 return analyzer.catchall
228
229 fn = getattr(analyzer, event.name, None)
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000230 if fn is None:
231 return analyzer.catchall
232
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530233 event_argcount = len(event.args)
Mads Ynddalce96eb32023-09-26 12:34:27 +0200234 fn_argcount = len(inspect.getfullargspec(fn)[0]) - 1
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000235 if fn_argcount == event_argcount + 1:
236 # Include timestamp as first argument
Stefan Hajnoczie42860a2018-02-22 16:39:01 +0000237 return lambda _, rec: fn(*(rec[1:2] + rec[3:3 + event_argcount]))
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200238 elif fn_argcount == event_argcount + 2:
239 # Include timestamp and pid
240 return lambda _, rec: fn(*rec[1:3 + event_argcount])
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000241 else:
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200242 # Just arguments, no timestamp or pid
243 return lambda _, rec: fn(*rec[3:3 + event_argcount])
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000244
Mads Ynddal87617b92023-09-26 12:34:30 +0200245 with analyzer:
246 fn_cache = {}
Mads Ynddalb78234e2023-09-26 12:34:32 +0200247 for event, event_id, timestamp_ns, record_pid, *rec_args in read_trace_records(events, log_fobj, read_header):
Mads Ynddal6f536412023-09-26 12:34:31 +0200248 if event_id not in fn_cache:
249 fn_cache[event_id] = build_fn(analyzer, event)
250 fn_cache[event_id](event, (event_id, timestamp_ns, record_pid, *rec_args))
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000251
252def run(analyzer):
253 """Execute an analyzer on a trace file given on the command-line.
254
255 This function is useful as a driver for simple analysis scripts. More
256 advanced scripts will want to call process() instead."""
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000257
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200258 try:
259 # NOTE: See built-in `argparse` module for a more robust cli interface
260 *no_header, trace_event_path, trace_file_path = sys.argv[1:]
261 assert no_header == [] or no_header == ['--no-header'], 'Invalid no-header argument'
262 except (AssertionError, ValueError):
Mads Ynddal1990fb92023-09-26 12:34:29 +0200263 raise SimpleException(f'usage: {sys.argv[0]} [--no-header] <trace-events> <trace-file>\n')
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000264
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200265 with open(trace_event_path, 'r') as events_fobj, open(trace_file_path, 'rb') as log_fobj:
266 process(events_fobj, log_fobj, analyzer, read_header=not no_header)
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000267
268if __name__ == '__main__':
269 class Formatter(Analyzer):
270 def __init__(self):
271 self.last_timestamp = None
272
273 def catchall(self, event, rec):
274 timestamp = rec[1]
275 if self.last_timestamp is None:
276 self.last_timestamp = timestamp
277 delta_ns = timestamp - self.last_timestamp
278 self.last_timestamp = timestamp
279
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200280 fields = [event.name, '%0.3f' % (delta_ns / 1000.0),
281 'pid=%d' % rec[2]]
282 i = 3
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530283 for type, name in event.args:
284 if is_string(type):
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200285 fields.append('%s=%s' % (name, rec[i]))
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530286 else:
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200287 fields.append('%s=0x%x' % (name, rec[i]))
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530288 i += 1
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300289 print(' '.join(fields))
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000290
Mads Ynddal1990fb92023-09-26 12:34:29 +0200291 try:
292 run(Formatter())
293 except SimpleException as e:
294 sys.stderr.write(str(e) + "\n")
295 sys.exit(1)