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