blob: 2ad56998ee67ca2e9d43beba3d8b25f1c06dd69b [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
Stefan Hajnoczi0b5538c2011-02-26 18:38:39 +000019dropped_event_id = 0xfffffffffffffffe
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010020
21trace_fmt = '=QQQQQQQQ'
22trace_len = struct.calcsize(trace_fmt)
Stefan Hajnoczi6df40082010-10-18 13:42:54 +010023event_re = re.compile(r'(disable\s+)?([a-zA-Z0-9_]+)\(([^)]*)\).*')
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010024
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010025def parse_events(fobj):
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000026 """Parse a trace-events file into {event_num: (name, arg1, ...)}."""
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010027
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
Stefan Hajnoczi0b5538c2011-02-26 18:38:39 +000032 events = {dropped_event_id: ('dropped', 'count')}
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010033 event_num = 0
34 for line in fobj:
35 m = event_re.match(line.strip())
36 if m is None:
37 continue
38
Stefan Hajnoczi6df40082010-10-18 13:42:54 +010039 disable, name, args = m.groups()
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010040 events[event_num] = (name,) + get_argnames(args)
41 event_num += 1
42 return events
43
44def read_record(fobj):
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000045 """Deserialize a trace record from a file into a tuple (event_num, timestamp, arg1, ..., arg6)."""
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010046 s = fobj.read(trace_len)
47 if len(s) != trace_len:
48 return None
49 return struct.unpack(trace_fmt, s)
50
51def read_trace_file(fobj):
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000052 """Deserialize trace records from a file, yielding record tuples (event_num, timestamp, arg1, ..., arg6)."""
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010053 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:
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000058 raise ValueError('not a trace file or incompatible version')
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010059
60 while True:
61 rec = read_record(fobj)
62 if rec is None:
63 break
64
65 yield rec
66
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000067class Analyzer(object):
68 """A trace file analyzer which processes trace records.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010069
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000070 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.
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010073
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000074 If a method matching a trace event name exists, it is invoked to process
75 that trace record. Otherwise the catchall() method is invoked."""
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010076
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000077 def begin(self):
78 """Called at the start of the trace."""
79 pass
Stefan Hajnoczi26f72272010-05-22 19:24:51 +010080
Stefan Hajnoczi59da6682011-02-22 13:59:41 +000081 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
89def 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 + fn_argcount])
106 else:
107 # Just arguments, no timestamp
108 return lambda _, rec: fn(*rec[2:2 + fn_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
120def 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
134if __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())