Statistics
| Branch: | Revision:

root / scripts / simpletrace.py @ a22f123c

History | View | Annotate | Download (4.6 kB)

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 + 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

    
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())