qdev: split out UI portions into a new function
[qemu.git] / scripts / simpletrace.py
1 #!/usr/bin/env python
2 #
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 #
10 # For help see docs/tracing.txt
11
12 import struct
13 import re
14 import inspect
15
16 header_event_id = 0xffffffffffffffff
17 header_magic = 0xf2b177cb0aa429b4
18 header_version = 0
19 dropped_event_id = 0xfffffffffffffffe
20
21 trace_fmt = '=QQQQQQQQ'
22 trace_len = struct.calcsize(trace_fmt)
23 event_re = re.compile(r'(disable\s+)?([a-zA-Z0-9_]+)\(([^)]*)\).*')
24
25 def parse_events(fobj):
26 """Parse a trace-events file into {event_num: (name, arg1, ...)}."""
27
28 def get_argnames(args):
29 """Extract argument names from a parameter list."""
30 return tuple(arg.split()[-1].lstrip('*') for arg in args.split(','))
31
32 events = {dropped_event_id: ('dropped', 'count')}
33 event_num = 0
34 for line in fobj:
35 m = event_re.match(line.strip())
36 if m is None:
37 continue
38
39 disable, name, args = m.groups()
40 events[event_num] = (name,) + get_argnames(args)
41 event_num += 1
42 return events
43
44 def read_record(fobj):
45 """Deserialize a trace record from a file into a tuple (event_num, timestamp, arg1, ..., arg6)."""
46 s = fobj.read(trace_len)
47 if len(s) != trace_len:
48 return None
49 return struct.unpack(trace_fmt, s)
50
51 def read_trace_file(fobj):
52 """Deserialize trace records from a file, yielding record tuples (event_num, timestamp, arg1, ..., arg6)."""
53 header = read_record(fobj)
54 if header is None or \
55 header[0] != header_event_id or \
56 header[1] != header_magic or \
57 header[2] != header_version:
58 raise ValueError('not a trace file or incompatible version')
59
60 while True:
61 rec = read_record(fobj)
62 if rec is None:
63 break
64
65 yield rec
66
67 class Analyzer(object):
68 """A trace file analyzer which processes trace records.
69
70 An analyzer can be passed to run() or process(). The begin() method is
71 invoked, then each trace record is processed, and finally the end() method
72 is invoked.
73
74 If a method matching a trace event name exists, it is invoked to process
75 that trace record. Otherwise the catchall() method is invoked."""
76
77 def begin(self):
78 """Called at the start of the trace."""
79 pass
80
81 def catchall(self, event, rec):
82 """Called if no specific method for processing a trace event has been found."""
83 pass
84
85 def end(self):
86 """Called at the end of the trace."""
87 pass
88
89 def process(events, log, analyzer):
90 """Invoke an analyzer on each event in a log."""
91 if isinstance(events, str):
92 events = parse_events(open(events, 'r'))
93 if isinstance(log, str):
94 log = open(log, 'rb')
95
96 def build_fn(analyzer, event):
97 fn = getattr(analyzer, event[0], None)
98 if fn is None:
99 return analyzer.catchall
100
101 event_argcount = len(event) - 1
102 fn_argcount = len(inspect.getargspec(fn)[0]) - 1
103 if fn_argcount == event_argcount + 1:
104 # Include timestamp as first argument
105 return lambda _, rec: fn(*rec[1:2 + event_argcount])
106 else:
107 # Just arguments, no timestamp
108 return lambda _, rec: fn(*rec[2:2 + event_argcount])
109
110 analyzer.begin()
111 fn_cache = {}
112 for rec in read_trace_file(log):
113 event_num = rec[0]
114 event = events[event_num]
115 if event_num not in fn_cache:
116 fn_cache[event_num] = build_fn(analyzer, event)
117 fn_cache[event_num](event, rec)
118 analyzer.end()
119
120 def run(analyzer):
121 """Execute an analyzer on a trace file given on the command-line.
122
123 This function is useful as a driver for simple analysis scripts. More
124 advanced scripts will want to call process() instead."""
125 import sys
126
127 if len(sys.argv) != 3:
128 sys.stderr.write('usage: %s <trace-events> <trace-file>\n' % sys.argv[0])
129 sys.exit(1)
130
131 events = parse_events(open(sys.argv[1], 'r'))
132 process(events, sys.argv[2], analyzer)
133
134 if __name__ == '__main__':
135 class Formatter(Analyzer):
136 def __init__(self):
137 self.last_timestamp = None
138
139 def catchall(self, event, rec):
140 timestamp = rec[1]
141 if self.last_timestamp is None:
142 self.last_timestamp = timestamp
143 delta_ns = timestamp - self.last_timestamp
144 self.last_timestamp = timestamp
145
146 fields = [event[0], '%0.3f' % (delta_ns / 1000.0)]
147 for i in xrange(1, len(event)):
148 fields.append('%s=0x%x' % (event[i], rec[i + 1]))
149 print ' '.join(fields)
150
151 run(Formatter())