blob: 7f514d15773a27d2480ccf9d5279ab611545cd45 [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'
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010034
Mads Ynddal1990fb92023-09-26 12:34:29 +020035class SimpleException(Exception):
36 pass
37
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053038def read_header(fobj, hfmt):
39 '''Read a trace record header'''
40 hlen = struct.calcsize(hfmt)
41 hdr = fobj.read(hlen)
42 if len(hdr) != hlen:
Mads Ynddal1990fb92023-09-26 12:34:29 +020043 raise SimpleException('Error reading header. Wrong filetype provided?')
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053044 return struct.unpack(hfmt, hdr)
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010045
Mads Ynddal3b71b612023-09-26 12:34:26 +020046def get_record(event_mapping, event_id_to_name, rechdr, fobj):
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010047 """Deserialize a trace record from a file into a tuple
48 (name, timestamp, pid, arg1, ..., arg6)."""
Mads Ynddald1f92592023-09-26 12:34:28 +020049 event_id, timestamp_ns, length, pid = rechdr
50 if event_id != dropped_event_id:
Mads Ynddal3b71b612023-09-26 12:34:26 +020051 name = event_id_to_name[event_id]
Jose Ricardo Ziviani249e9f72017-05-29 13:30:04 -030052 try:
Mads Ynddal3b71b612023-09-26 12:34:26 +020053 event = event_mapping[name]
Eduardo Habkostbd228082018-06-08 09:29:51 -030054 except KeyError as e:
Mads Ynddal1990fb92023-09-26 12:34:29 +020055 raise SimpleException(
56 f'{e} event is logged but is not declared in the trace events'
57 'file, try using trace-events-all instead.'
58 )
Jose Ricardo Ziviani249e9f72017-05-29 13:30:04 -030059
Mads Ynddald1f92592023-09-26 12:34:28 +020060 rec = (name, timestamp_ns, pid)
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053061 for type, name in event.args:
62 if is_string(type):
63 l = fobj.read(4)
64 (len,) = struct.unpack('=L', l)
65 s = fobj.read(len)
66 rec = rec + (s,)
67 else:
68 (value,) = struct.unpack('=Q', fobj.read(8))
69 rec = rec + (value,)
70 else:
Mads Ynddald1f92592023-09-26 12:34:28 +020071 (dropped_count,) = struct.unpack('=Q', fobj.read(8))
72 rec = ("dropped", timestamp_ns, pid, dropped_count)
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053073 return rec
74
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010075def get_mapping(fobj):
76 (event_id, ) = struct.unpack('=Q', fobj.read(8))
77 (len, ) = struct.unpack('=L', fobj.read(4))
Eduardo Habkost749c1d82018-06-19 16:45:49 -030078 name = fobj.read(len).decode()
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053079
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010080 return (event_id, name)
81
Mads Ynddal3b71b612023-09-26 12:34:26 +020082def read_record(event_mapping, event_id_to_name, fobj):
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +020083 """Deserialize a trace record from a file into a tuple (event_num, timestamp, pid, arg1, ..., arg6)."""
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053084 rechdr = read_header(fobj, rec_header_fmt)
Mads Ynddal3b71b612023-09-26 12:34:26 +020085 return get_record(event_mapping, event_id_to_name, rechdr, fobj)
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053086
Stefan Hajnoczi15327c32014-06-22 21:46:06 +080087def read_trace_header(fobj):
88 """Read and verify trace file header"""
Mads Ynddald1f92592023-09-26 12:34:28 +020089 _header_event_id, _header_magic, log_version = read_header(fobj, log_header_fmt)
90 if _header_event_id != header_event_id:
91 raise ValueError(f'Not a valid trace file, header id {_header_event_id} != {header_event_id}')
92 if _header_magic != header_magic:
93 raise ValueError(f'Not a valid trace file, header magic {_header_magic} != {header_magic}')
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +053094
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010095 if log_version not in [0, 2, 3, 4]:
Mads Ynddald1f92592023-09-26 12:34:28 +020096 raise ValueError(f'Unknown version {log_version} of tracelog format!')
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +010097 if log_version != 4:
Mads Ynddald1f92592023-09-26 12:34:28 +020098 raise ValueError(f'Log format {log_version} not supported with this QEMU release!')
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010099
Mads Ynddal3b71b612023-09-26 12:34:26 +0200100def read_trace_records(event_mapping, event_id_to_name, fobj):
Stefan Hajnoczi840d8352017-08-15 09:44:30 +0100101 """Deserialize trace records from a file, yielding record tuples (event_num, timestamp, pid, arg1, ..., arg6).
102
Mads Ynddal3b71b612023-09-26 12:34:26 +0200103 Note that `event_id_to_name` is modified if the file contains mapping records.
Stefan Hajnoczi840d8352017-08-15 09:44:30 +0100104
105 Args:
Mads Ynddal3b71b612023-09-26 12:34:26 +0200106 event_mapping (str -> Event): events dict, indexed by name
107 event_id_to_name (int -> str): event names dict, indexed by event ID
Stefan Hajnoczi840d8352017-08-15 09:44:30 +0100108 fobj (file): input file
109
110 """
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100111 while True:
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100112 t = fobj.read(8)
113 if len(t) == 0:
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100114 break
115
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100116 (rectype, ) = struct.unpack('=Q', t)
117 if rectype == record_type_mapping:
118 event_id, name = get_mapping(fobj)
Mads Ynddal3b71b612023-09-26 12:34:26 +0200119 event_id_to_name[event_id] = name
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100120 else:
Mads Ynddal3b71b612023-09-26 12:34:26 +0200121 rec = read_record(event_mapping, event_id_to_name, fobj)
Daniel P. Berrange7f1b5882016-10-04 14:35:50 +0100122
123 yield rec
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100124
Mads Ynddal87617b92023-09-26 12:34:30 +0200125class Analyzer:
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000126 """A trace file analyzer which processes trace records.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100127
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000128 An analyzer can be passed to run() or process(). The begin() method is
129 invoked, then each trace record is processed, and finally the end() method
Mads Ynddal87617b92023-09-26 12:34:30 +0200130 is invoked. When Analyzer is used as a context-manager (using the `with`
131 statement), begin() and end() are called automatically.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100132
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000133 If a method matching a trace event name exists, it is invoked to process
Stefan Hajnoczi659370f2017-04-11 10:56:54 +0100134 that trace record. Otherwise the catchall() method is invoked.
135
136 Example:
137 The following method handles the runstate_set(int new_state) trace event::
138
139 def runstate_set(self, new_state):
140 ...
141
142 The method can also take a timestamp argument before the trace event
143 arguments::
144
145 def runstate_set(self, timestamp, new_state):
146 ...
147
148 Timestamps have the uint64_t type and are in nanoseconds.
149
150 The pid can be included in addition to the timestamp and is useful when
151 dealing with traces from multiple processes::
152
153 def runstate_set(self, timestamp, pid, new_state):
154 ...
155 """
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100156
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000157 def begin(self):
158 """Called at the start of the trace."""
159 pass
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100160
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000161 def catchall(self, event, rec):
162 """Called if no specific method for processing a trace event has been found."""
163 pass
164
165 def end(self):
166 """Called at the end of the trace."""
167 pass
168
Mads Ynddal87617b92023-09-26 12:34:30 +0200169 def __enter__(self):
170 self.begin()
171 return self
172
173 def __exit__(self, exc_type, exc_val, exc_tb):
174 if exc_type is None:
175 self.end()
176 return False
177
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800178def process(events, log, analyzer, read_header=True):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200179 """Invoke an analyzer on each event in a log.
180 Args:
181 events (file-object or list or str): events list or file-like object or file path as str to read event data from
182 log (file-object or str): file-like object or file path as str to read log data from
183 analyzer (Analyzer): Instance of Analyzer to interpret the event data
184 read_header (bool, optional): Whether to read header data from the log data. Defaults to True.
185 """
186
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000187 if isinstance(events, str):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200188 with open(events, 'r') as f:
189 events_list = read_events(f, events)
190 elif isinstance(events, list):
191 # Treat as a list of events already produced by tracetool.read_events
192 events_list = events
193 else:
194 # Treat as an already opened file-object
195 events_list = read_events(events, events.name)
196
197 close_log = False
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000198 if isinstance(log, str):
199 log = open(log, 'rb')
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200200 close_log = True
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000201
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800202 if read_header:
203 read_trace_header(log)
204
Volker Rümelinc6e93c92021-01-31 18:34:15 +0100205 frameinfo = inspect.getframeinfo(inspect.currentframe())
206 dropped_event = Event.build("Dropped_Event(uint64_t num_events_dropped)",
207 frameinfo.lineno + 1, frameinfo.filename)
Mads Ynddal3b71b612023-09-26 12:34:26 +0200208 event_mapping = {"dropped": dropped_event}
209 event_id_to_name = {dropped_event_id: "dropped"}
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530210
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200211 for event in events_list:
Mads Ynddal3b71b612023-09-26 12:34:26 +0200212 event_mapping[event.name] = event
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530213
Stefan Hajnoczi840d8352017-08-15 09:44:30 +0100214 # If there is no header assume event ID mapping matches events list
215 if not read_header:
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200216 for event_id, event in enumerate(events_list):
Mads Ynddal3b71b612023-09-26 12:34:26 +0200217 event_id_to_name[event_id] = event.name
Stefan Hajnoczi840d8352017-08-15 09:44:30 +0100218
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000219 def build_fn(analyzer, event):
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530220 if isinstance(event, str):
221 return analyzer.catchall
222
223 fn = getattr(analyzer, event.name, None)
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000224 if fn is None:
225 return analyzer.catchall
226
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530227 event_argcount = len(event.args)
Mads Ynddalce96eb32023-09-26 12:34:27 +0200228 fn_argcount = len(inspect.getfullargspec(fn)[0]) - 1
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000229 if fn_argcount == event_argcount + 1:
230 # Include timestamp as first argument
Stefan Hajnoczie42860a2018-02-22 16:39:01 +0000231 return lambda _, rec: fn(*(rec[1:2] + rec[3:3 + event_argcount]))
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200232 elif fn_argcount == event_argcount + 2:
233 # Include timestamp and pid
234 return lambda _, rec: fn(*rec[1:3 + event_argcount])
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000235 else:
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200236 # Just arguments, no timestamp or pid
237 return lambda _, rec: fn(*rec[3:3 + event_argcount])
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000238
Mads Ynddal87617b92023-09-26 12:34:30 +0200239 with analyzer:
240 fn_cache = {}
241 for rec in read_trace_records(event_mapping, event_id_to_name, log):
242 event_num = rec[0]
243 event = event_mapping[event_num]
244 if event_num not in fn_cache:
245 fn_cache[event_num] = build_fn(analyzer, event)
246 fn_cache[event_num](event, rec)
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000247
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200248 if close_log:
249 log.close()
250
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000251def run(analyzer):
252 """Execute an analyzer on a trace file given on the command-line.
253
254 This function is useful as a driver for simple analysis scripts. More
255 advanced scripts will want to call process() instead."""
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000256
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200257 try:
258 # NOTE: See built-in `argparse` module for a more robust cli interface
259 *no_header, trace_event_path, trace_file_path = sys.argv[1:]
260 assert no_header == [] or no_header == ['--no-header'], 'Invalid no-header argument'
261 except (AssertionError, ValueError):
Mads Ynddal1990fb92023-09-26 12:34:29 +0200262 raise SimpleException(f'usage: {sys.argv[0]} [--no-header] <trace-events> <trace-file>\n')
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000263
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200264 with open(trace_event_path, 'r') as events_fobj, open(trace_file_path, 'rb') as log_fobj:
265 process(events_fobj, log_fobj, analyzer, read_header=not no_header)
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000266
267if __name__ == '__main__':
268 class Formatter(Analyzer):
269 def __init__(self):
270 self.last_timestamp = None
271
272 def catchall(self, event, rec):
273 timestamp = rec[1]
274 if self.last_timestamp is None:
275 self.last_timestamp = timestamp
276 delta_ns = timestamp - self.last_timestamp
277 self.last_timestamp = timestamp
278
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200279 fields = [event.name, '%0.3f' % (delta_ns / 1000.0),
280 'pid=%d' % rec[2]]
281 i = 3
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530282 for type, name in event.args:
283 if is_string(type):
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200284 fields.append('%s=%s' % (name, rec[i]))
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530285 else:
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200286 fields.append('%s=0x%x' % (name, rec[i]))
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530287 i += 1
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300288 print(' '.join(fields))
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000289
Mads Ynddal1990fb92023-09-26 12:34:29 +0200290 try:
291 run(Formatter())
292 except SimpleException as e:
293 sys.stderr.write(str(e) + "\n")
294 sys.exit(1)