|
1 import _hotshot |
|
2 import os.path |
|
3 import parser |
|
4 import symbol |
|
5 import sys |
|
6 |
|
7 from _hotshot import \ |
|
8 WHAT_ENTER, \ |
|
9 WHAT_EXIT, \ |
|
10 WHAT_LINENO, \ |
|
11 WHAT_DEFINE_FILE, \ |
|
12 WHAT_DEFINE_FUNC, \ |
|
13 WHAT_ADD_INFO |
|
14 |
|
15 |
|
16 __all__ = ["LogReader", "ENTER", "EXIT", "LINE"] |
|
17 |
|
18 |
|
19 ENTER = WHAT_ENTER |
|
20 EXIT = WHAT_EXIT |
|
21 LINE = WHAT_LINENO |
|
22 |
|
23 |
|
24 class LogReader: |
|
25 def __init__(self, logfn): |
|
26 # fileno -> filename |
|
27 self._filemap = {} |
|
28 # (fileno, lineno) -> filename, funcname |
|
29 self._funcmap = {} |
|
30 |
|
31 self._reader = _hotshot.logreader(logfn) |
|
32 self._nextitem = self._reader.next |
|
33 self._info = self._reader.info |
|
34 if self._info.has_key('current-directory'): |
|
35 self.cwd = self._info['current-directory'] |
|
36 else: |
|
37 self.cwd = None |
|
38 |
|
39 # This mirrors the call stack of the profiled code as the log |
|
40 # is read back in. It contains tuples of the form: |
|
41 # |
|
42 # (file name, line number of function def, function name) |
|
43 # |
|
44 self._stack = [] |
|
45 self._append = self._stack.append |
|
46 self._pop = self._stack.pop |
|
47 |
|
48 def close(self): |
|
49 self._reader.close() |
|
50 |
|
51 def fileno(self): |
|
52 """Return the file descriptor of the log reader's log file.""" |
|
53 return self._reader.fileno() |
|
54 |
|
55 def addinfo(self, key, value): |
|
56 """This method is called for each additional ADD_INFO record. |
|
57 |
|
58 This can be overridden by applications that want to receive |
|
59 these events. The default implementation does not need to be |
|
60 called by alternate implementations. |
|
61 |
|
62 The initial set of ADD_INFO records do not pass through this |
|
63 mechanism; this is only needed to receive notification when |
|
64 new values are added. Subclasses can inspect self._info after |
|
65 calling LogReader.__init__(). |
|
66 """ |
|
67 pass |
|
68 |
|
69 def get_filename(self, fileno): |
|
70 try: |
|
71 return self._filemap[fileno] |
|
72 except KeyError: |
|
73 raise ValueError, "unknown fileno" |
|
74 |
|
75 def get_filenames(self): |
|
76 return self._filemap.values() |
|
77 |
|
78 def get_fileno(self, filename): |
|
79 filename = os.path.normcase(os.path.normpath(filename)) |
|
80 for fileno, name in self._filemap.items(): |
|
81 if name == filename: |
|
82 return fileno |
|
83 raise ValueError, "unknown filename" |
|
84 |
|
85 def get_funcname(self, fileno, lineno): |
|
86 try: |
|
87 return self._funcmap[(fileno, lineno)] |
|
88 except KeyError: |
|
89 raise ValueError, "unknown function location" |
|
90 |
|
91 # Iteration support: |
|
92 # This adds an optional (& ignored) parameter to next() so that the |
|
93 # same bound method can be used as the __getitem__() method -- this |
|
94 # avoids using an additional method call which kills the performance. |
|
95 |
|
96 def next(self, index=0): |
|
97 while 1: |
|
98 # This call may raise StopIteration: |
|
99 what, tdelta, fileno, lineno = self._nextitem() |
|
100 |
|
101 # handle the most common cases first |
|
102 |
|
103 if what == WHAT_ENTER: |
|
104 filename, funcname = self._decode_location(fileno, lineno) |
|
105 t = (filename, lineno, funcname) |
|
106 self._append(t) |
|
107 return what, t, tdelta |
|
108 |
|
109 if what == WHAT_EXIT: |
|
110 return what, self._pop(), tdelta |
|
111 |
|
112 if what == WHAT_LINENO: |
|
113 filename, firstlineno, funcname = self._stack[-1] |
|
114 return what, (filename, lineno, funcname), tdelta |
|
115 |
|
116 if what == WHAT_DEFINE_FILE: |
|
117 filename = os.path.normcase(os.path.normpath(tdelta)) |
|
118 self._filemap[fileno] = filename |
|
119 elif what == WHAT_DEFINE_FUNC: |
|
120 filename = self._filemap[fileno] |
|
121 self._funcmap[(fileno, lineno)] = (filename, tdelta) |
|
122 elif what == WHAT_ADD_INFO: |
|
123 # value already loaded into self.info; call the |
|
124 # overridable addinfo() handler so higher-level code |
|
125 # can pick up the new value |
|
126 if tdelta == 'current-directory': |
|
127 self.cwd = lineno |
|
128 self.addinfo(tdelta, lineno) |
|
129 else: |
|
130 raise ValueError, "unknown event type" |
|
131 |
|
132 def __iter__(self): |
|
133 return self |
|
134 |
|
135 # |
|
136 # helpers |
|
137 # |
|
138 |
|
139 def _decode_location(self, fileno, lineno): |
|
140 try: |
|
141 return self._funcmap[(fileno, lineno)] |
|
142 except KeyError: |
|
143 # |
|
144 # This should only be needed when the log file does not |
|
145 # contain all the DEFINE_FUNC records needed to allow the |
|
146 # function name to be retrieved from the log file. |
|
147 # |
|
148 if self._loadfile(fileno): |
|
149 filename = funcname = None |
|
150 try: |
|
151 filename, funcname = self._funcmap[(fileno, lineno)] |
|
152 except KeyError: |
|
153 filename = self._filemap.get(fileno) |
|
154 funcname = None |
|
155 self._funcmap[(fileno, lineno)] = (filename, funcname) |
|
156 return filename, funcname |
|
157 |
|
158 def _loadfile(self, fileno): |
|
159 try: |
|
160 filename = self._filemap[fileno] |
|
161 except KeyError: |
|
162 print "Could not identify fileId", fileno |
|
163 return 1 |
|
164 if filename is None: |
|
165 return 1 |
|
166 absname = os.path.normcase(os.path.join(self.cwd, filename)) |
|
167 |
|
168 try: |
|
169 fp = open(absname) |
|
170 except IOError: |
|
171 return |
|
172 st = parser.suite(fp.read()) |
|
173 fp.close() |
|
174 |
|
175 # Scan the tree looking for def and lambda nodes, filling in |
|
176 # self._funcmap with all the available information. |
|
177 funcdef = symbol.funcdef |
|
178 lambdef = symbol.lambdef |
|
179 |
|
180 stack = [st.totuple(1)] |
|
181 |
|
182 while stack: |
|
183 tree = stack.pop() |
|
184 try: |
|
185 sym = tree[0] |
|
186 except (IndexError, TypeError): |
|
187 continue |
|
188 if sym == funcdef: |
|
189 self._funcmap[(fileno, tree[2][2])] = filename, tree[2][1] |
|
190 elif sym == lambdef: |
|
191 self._funcmap[(fileno, tree[1][2])] = filename, "<lambda>" |
|
192 stack.extend(list(tree[1:])) |