Commit | Line | Data |
---|---|---|
920dae64 AT |
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" |