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