blob: 229b10aa990a2c18f48b33b01d5138ede4e02f24 [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
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000125class Analyzer(object):
126 """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
130 is invoked.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100131
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000132 If a method matching a trace event name exists, it is invoked to process
Stefan Hajnoczi659370f2017-04-11 10:56:54 +0100133 that trace record. Otherwise the catchall() method is invoked.
134
135 Example:
136 The following method handles the runstate_set(int new_state) trace event::
137
138 def runstate_set(self, new_state):
139 ...
140
141 The method can also take a timestamp argument before the trace event
142 arguments::
143
144 def runstate_set(self, timestamp, new_state):
145 ...
146
147 Timestamps have the uint64_t type and are in nanoseconds.
148
149 The pid can be included in addition to the timestamp and is useful when
150 dealing with traces from multiple processes::
151
152 def runstate_set(self, timestamp, pid, new_state):
153 ...
154 """
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100155
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000156 def begin(self):
157 """Called at the start of the trace."""
158 pass
Stefan Hajnoczi26f72272010-05-22 19:24:51 +0100159
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000160 def catchall(self, event, rec):
161 """Called if no specific method for processing a trace event has been found."""
162 pass
163
164 def end(self):
165 """Called at the end of the trace."""
166 pass
167
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800168def process(events, log, analyzer, read_header=True):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200169 """Invoke an analyzer on each event in a log.
170 Args:
171 events (file-object or list or str): events list or file-like object or file path as str to read event data from
172 log (file-object or str): file-like object or file path as str to read log data from
173 analyzer (Analyzer): Instance of Analyzer to interpret the event data
174 read_header (bool, optional): Whether to read header data from the log data. Defaults to True.
175 """
176
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000177 if isinstance(events, str):
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200178 with open(events, 'r') as f:
179 events_list = read_events(f, events)
180 elif isinstance(events, list):
181 # Treat as a list of events already produced by tracetool.read_events
182 events_list = events
183 else:
184 # Treat as an already opened file-object
185 events_list = read_events(events, events.name)
186
187 close_log = False
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000188 if isinstance(log, str):
189 log = open(log, 'rb')
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200190 close_log = True
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000191
Stefan Hajnoczi15327c32014-06-22 21:46:06 +0800192 if read_header:
193 read_trace_header(log)
194
Volker Rümelinc6e93c92021-01-31 18:34:15 +0100195 frameinfo = inspect.getframeinfo(inspect.currentframe())
196 dropped_event = Event.build("Dropped_Event(uint64_t num_events_dropped)",
197 frameinfo.lineno + 1, frameinfo.filename)
Mads Ynddal3b71b612023-09-26 12:34:26 +0200198 event_mapping = {"dropped": dropped_event}
199 event_id_to_name = {dropped_event_id: "dropped"}
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530200
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200201 for event in events_list:
Mads Ynddal3b71b612023-09-26 12:34:26 +0200202 event_mapping[event.name] = event
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530203
Stefan Hajnoczi840d8352017-08-15 09:44:30 +0100204 # If there is no header assume event ID mapping matches events list
205 if not read_header:
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200206 for event_id, event in enumerate(events_list):
Mads Ynddal3b71b612023-09-26 12:34:26 +0200207 event_id_to_name[event_id] = event.name
Stefan Hajnoczi840d8352017-08-15 09:44:30 +0100208
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000209 def build_fn(analyzer, event):
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530210 if isinstance(event, str):
211 return analyzer.catchall
212
213 fn = getattr(analyzer, event.name, None)
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000214 if fn is None:
215 return analyzer.catchall
216
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530217 event_argcount = len(event.args)
Mads Ynddalce96eb32023-09-26 12:34:27 +0200218 fn_argcount = len(inspect.getfullargspec(fn)[0]) - 1
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000219 if fn_argcount == event_argcount + 1:
220 # Include timestamp as first argument
Stefan Hajnoczie42860a2018-02-22 16:39:01 +0000221 return lambda _, rec: fn(*(rec[1:2] + rec[3:3 + event_argcount]))
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200222 elif fn_argcount == event_argcount + 2:
223 # Include timestamp and pid
224 return lambda _, rec: fn(*rec[1:3 + event_argcount])
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000225 else:
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200226 # Just arguments, no timestamp or pid
227 return lambda _, rec: fn(*rec[3:3 + event_argcount])
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000228
229 analyzer.begin()
230 fn_cache = {}
Mads Ynddal3b71b612023-09-26 12:34:26 +0200231 for rec in read_trace_records(event_mapping, event_id_to_name, log):
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000232 event_num = rec[0]
Mads Ynddal3b71b612023-09-26 12:34:26 +0200233 event = event_mapping[event_num]
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000234 if event_num not in fn_cache:
235 fn_cache[event_num] = build_fn(analyzer, event)
236 fn_cache[event_num](event, rec)
237 analyzer.end()
238
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200239 if close_log:
240 log.close()
241
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000242def run(analyzer):
243 """Execute an analyzer on a trace file given on the command-line.
244
245 This function is useful as a driver for simple analysis scripts. More
246 advanced scripts will want to call process() instead."""
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000247
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200248 try:
249 # NOTE: See built-in `argparse` module for a more robust cli interface
250 *no_header, trace_event_path, trace_file_path = sys.argv[1:]
251 assert no_header == [] or no_header == ['--no-header'], 'Invalid no-header argument'
252 except (AssertionError, ValueError):
Mads Ynddal1990fb92023-09-26 12:34:29 +0200253 raise SimpleException(f'usage: {sys.argv[0]} [--no-header] <trace-events> <trace-file>\n')
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000254
Mads Ynddalf7bd4f02023-09-26 12:34:25 +0200255 with open(trace_event_path, 'r') as events_fobj, open(trace_file_path, 'rb') as log_fobj:
256 process(events_fobj, log_fobj, analyzer, read_header=not no_header)
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000257
258if __name__ == '__main__':
259 class Formatter(Analyzer):
260 def __init__(self):
261 self.last_timestamp = None
262
263 def catchall(self, event, rec):
264 timestamp = rec[1]
265 if self.last_timestamp is None:
266 self.last_timestamp = timestamp
267 delta_ns = timestamp - self.last_timestamp
268 self.last_timestamp = timestamp
269
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200270 fields = [event.name, '%0.3f' % (delta_ns / 1000.0),
271 'pid=%d' % rec[2]]
272 i = 3
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530273 for type, name in event.args:
274 if is_string(type):
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200275 fields.append('%s=%s' % (name, rec[i]))
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530276 else:
Stefan Hajnoczi80ff35c2014-05-07 19:24:11 +0200277 fields.append('%s=0x%x' % (name, rec[i]))
Harsh Prateek Bora90a147a2012-07-18 15:16:00 +0530278 i += 1
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300279 print(' '.join(fields))
Stefan Hajnoczi59da6682011-02-22 13:59:41 +0000280
Mads Ynddal1990fb92023-09-26 12:34:29 +0200281 try:
282 run(Formatter())
283 except SimpleException as e:
284 sys.stderr.write(str(e) + "\n")
285 sys.exit(1)