blob: 9fe3dda0769ae49a5d3b872d7e6258fd0b3fc68d [file] [log] [blame]
Stefan Hajnoczi26f72272010-05-22 19:24:51 +01001#!/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
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010012import struct
13import re
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000014import inspect
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010015
16header_event_id = 0xffffffffffffffff
17header_magic = 0xf2b177cb0aa429b4
18header_version = 0
19
20trace_fmt = '=QQQQQQQQ'
21trace_len = struct.calcsize(trace_fmt)
Stefan Hajnoczi6df40082010-10-18 13:42:54 +010022event_re = re.compile(r'(disable\s+)?([a-zA-Z0-9_]+)\(([^)]*)\).*')
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010023
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010024def parse_events(fobj):
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000025 """Parse a trace-events file into {event_num: (name, arg1, ...)}."""
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010026
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
Stefan Hajnoczi6df40082010-10-18 13:42:54 +010038 disable, name, args = m.groups()
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010039 events[event_num] = (name,) + get_argnames(args)
40 event_num += 1
41 return events
42
43def read_record(fobj):
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000044 """Deserialize a trace record from a file into a tuple (event_num, timestamp, arg1, ..., arg6)."""
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010045 s = fobj.read(trace_len)
46 if len(s) != trace_len:
47 return None
48 return struct.unpack(trace_fmt, s)
49
50def read_trace_file(fobj):
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000051 """Deserialize trace records from a file, yielding record tuples (event_num, timestamp, arg1, ..., arg6)."""
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010052 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:
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000057 raise ValueError('not a trace file or incompatible version')
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010058
59 while True:
60 rec = read_record(fobj)
61 if rec is None:
62 break
63
64 yield rec
65
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000066class Analyzer(object):
67 """A trace file analyzer which processes trace records.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010068
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000069 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.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010072
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000073 If a method matching a trace event name exists, it is invoked to process
74 that trace record. Otherwise the catchall() method is invoked."""
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010075
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000076 def begin(self):
77 """Called at the start of the trace."""
78 pass
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010079
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000080 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
88def 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
119def 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
133if __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())