1 | """Statistics analyzer for HotShot."""
|
---|
2 |
|
---|
3 | import profile
|
---|
4 | import pstats
|
---|
5 |
|
---|
6 | import hotshot.log
|
---|
7 |
|
---|
8 | from hotshot.log import ENTER, EXIT
|
---|
9 |
|
---|
10 |
|
---|
11 | def load(filename):
|
---|
12 | return StatsLoader(filename).load()
|
---|
13 |
|
---|
14 |
|
---|
15 | class StatsLoader:
|
---|
16 | def __init__(self, logfn):
|
---|
17 | self._logfn = logfn
|
---|
18 | self._code = {}
|
---|
19 | self._stack = []
|
---|
20 | self.pop_frame = self._stack.pop
|
---|
21 |
|
---|
22 | def load(self):
|
---|
23 | # The timer selected by the profiler should never be used, so make
|
---|
24 | # sure it doesn't work:
|
---|
25 | p = Profile()
|
---|
26 | p.get_time = _brokentimer
|
---|
27 | log = hotshot.log.LogReader(self._logfn)
|
---|
28 | taccum = 0
|
---|
29 | for event in log:
|
---|
30 | what, (filename, lineno, funcname), tdelta = event
|
---|
31 | if tdelta > 0:
|
---|
32 | taccum += tdelta
|
---|
33 |
|
---|
34 | # We multiply taccum to convert from the microseconds we
|
---|
35 | # have to the seconds that the profile/pstats module work
|
---|
36 | # with; this allows the numbers to have some basis in
|
---|
37 | # reality (ignoring calibration issues for now).
|
---|
38 |
|
---|
39 | if what == ENTER:
|
---|
40 | frame = self.new_frame(filename, lineno, funcname)
|
---|
41 | p.trace_dispatch_call(frame, taccum * .000001)
|
---|
42 | taccum = 0
|
---|
43 |
|
---|
44 | elif what == EXIT:
|
---|
45 | frame = self.pop_frame()
|
---|
46 | p.trace_dispatch_return(frame, taccum * .000001)
|
---|
47 | taccum = 0
|
---|
48 |
|
---|
49 | # no further work for line events
|
---|
50 |
|
---|
51 | assert not self._stack
|
---|
52 | return pstats.Stats(p)
|
---|
53 |
|
---|
54 | def new_frame(self, *args):
|
---|
55 | # args must be filename, firstlineno, funcname
|
---|
56 | # our code objects are cached since we don't need to create
|
---|
57 | # new ones every time
|
---|
58 | try:
|
---|
59 | code = self._code[args]
|
---|
60 | except KeyError:
|
---|
61 | code = FakeCode(*args)
|
---|
62 | self._code[args] = code
|
---|
63 | # frame objects are create fresh, since the back pointer will
|
---|
64 | # vary considerably
|
---|
65 | if self._stack:
|
---|
66 | back = self._stack[-1]
|
---|
67 | else:
|
---|
68 | back = None
|
---|
69 | frame = FakeFrame(code, back)
|
---|
70 | self._stack.append(frame)
|
---|
71 | return frame
|
---|
72 |
|
---|
73 |
|
---|
74 | class Profile(profile.Profile):
|
---|
75 | def simulate_cmd_complete(self):
|
---|
76 | pass
|
---|
77 |
|
---|
78 |
|
---|
79 | class FakeCode:
|
---|
80 | def __init__(self, filename, firstlineno, funcname):
|
---|
81 | self.co_filename = filename
|
---|
82 | self.co_firstlineno = firstlineno
|
---|
83 | self.co_name = self.__name__ = funcname
|
---|
84 |
|
---|
85 |
|
---|
86 | class FakeFrame:
|
---|
87 | def __init__(self, code, back):
|
---|
88 | self.f_back = back
|
---|
89 | self.f_code = code
|
---|
90 |
|
---|
91 |
|
---|
92 | def _brokentimer():
|
---|
93 | raise RuntimeError, "this timer should not be called"
|
---|