blob: 9e2f0688969b2cd43fd6b5722392caff4f1010d1 [file] [log] [blame]
Fred Drakef0193242001-10-12 20:56:29 +00001import _hotshot
2import os.path
3import parser
4import symbol
5import sys
6
7from _hotshot import \
8 WHAT_ENTER, \
9 WHAT_EXIT, \
10 WHAT_LINENO, \
11 WHAT_DEFINE_FILE, \
Fred Draked5d5a042001-10-15 22:05:32 +000012 WHAT_DEFINE_FUNC, \
Fred Drakef0193242001-10-12 20:56:29 +000013 WHAT_ADD_INFO
14
15
16__all__ = ["LogReader", "ENTER", "EXIT", "LINE"]
17
18
19ENTER = WHAT_ENTER
20EXIT = WHAT_EXIT
21LINE = WHAT_LINENO
22
23
24try:
25 StopIteration
26except NameError:
27 StopIteration = IndexError
28
29
30class LogReader:
31 def __init__(self, logfn):
32 # fileno -> filename
33 self._filemap = {}
34 # (fileno, lineno) -> filename, funcname
35 self._funcmap = {}
36
Fred Draked62f1512001-10-13 02:55:40 +000037 self._reader = _hotshot.logreader(logfn)
38 self._nextitem = self._reader.next
Fred Drake8e26b522001-10-29 20:57:23 +000039 self._info = self._reader.info
Fred Drake8b688622002-03-12 14:26:37 +000040 if self._info.has_key('current-directory'):
41 self.cwd = self._info['current-directory']
42 else:
43 self.cwd = None
Fred Drake127ee162002-05-29 19:40:36 +000044
45 # This mirrors the call stack of the profiled code as the log
46 # is read back in. It contains tuples of the form:
47 #
48 # (file name, line number of function def, function name)
49 #
Fred Drakef0193242001-10-12 20:56:29 +000050 self._stack = []
Fred Drake8b688622002-03-12 14:26:37 +000051 self._append = self._stack.append
52 self._pop = self._stack.pop
Fred Drakef0193242001-10-12 20:56:29 +000053
Fred Drake8e26b522001-10-29 20:57:23 +000054 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
Fred Drake8b688622002-03-12 14:26:37 +000068 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
Fred Drakef0193242001-10-12 20:56:29 +000090 # 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):
Fred Draked5d5a042001-10-15 22:05:32 +000096 while 1:
Fred Drakef0193242001-10-12 20:56:29 +000097 try:
Fred Draked5d5a042001-10-15 22:05:32 +000098 what, tdelta, fileno, lineno = self._nextitem()
99 except TypeError:
100 # logreader().next() returns None at the end
101 self._reader.close()
102 raise StopIteration()
Fred Drake8b688622002-03-12 14:26:37 +0000103
104 # handle the most common cases first
105
106 if what == WHAT_ENTER:
107 filename, funcname = self._decode_location(fileno, lineno)
Fred Drake127ee162002-05-29 19:40:36 +0000108 t = (filename, lineno, funcname)
109 self._append(t)
110 return what, t, tdelta
Fred Drake8b688622002-03-12 14:26:37 +0000111
112 if what == WHAT_EXIT:
Fred Drake127ee162002-05-29 19:40:36 +0000113 return what, self._pop(), tdelta
Fred Drake8b688622002-03-12 14:26:37 +0000114
115 if what == WHAT_LINENO:
Fred Drake127ee162002-05-29 19:40:36 +0000116 filename, firstlineno, funcname = self._stack[-1]
Fred Drake8b688622002-03-12 14:26:37 +0000117 return what, (filename, lineno, funcname), tdelta
118
Fred Draked5d5a042001-10-15 22:05:32 +0000119 if what == WHAT_DEFINE_FILE:
Fred Drake8b688622002-03-12 14:26:37 +0000120 filename = os.path.normcase(os.path.normpath(tdelta))
121 self._filemap[fileno] = filename
122 elif what == WHAT_DEFINE_FUNC:
Fred Draked5d5a042001-10-15 22:05:32 +0000123 filename = self._filemap[fileno]
124 self._funcmap[(fileno, lineno)] = (filename, tdelta)
Fred Drake8b688622002-03-12 14:26:37 +0000125 elif what == WHAT_ADD_INFO:
Fred Drake8e26b522001-10-29 20:57:23 +0000126 # value already loaded into self.info; call the
127 # overridable addinfo() handler so higher-level code
128 # can pick up the new value
Fred Drake8b688622002-03-12 14:26:37 +0000129 if tdelta == 'current-directory':
130 self.cwd = lineno
Fred Drake8e26b522001-10-29 20:57:23 +0000131 self.addinfo(tdelta, lineno)
Fred Draked5d5a042001-10-15 22:05:32 +0000132 else:
Fred Drake8b688622002-03-12 14:26:37 +0000133 raise ValueError, "unknown event type"
Fred Drakef0193242001-10-12 20:56:29 +0000134
135 if sys.version < "2.2":
136 # Don't add this for newer Python versions; we only want iteration
137 # support, not general sequence support.
138 __getitem__ = next
139 else:
140 def __iter__(self):
141 return self
142
143 #
144 # helpers
145 #
146
147 def _decode_location(self, fileno, lineno):
148 try:
149 return self._funcmap[(fileno, lineno)]
150 except KeyError:
Fred Draked5d5a042001-10-15 22:05:32 +0000151 #
152 # This should only be needed when the log file does not
153 # contain all the DEFINE_FUNC records needed to allow the
154 # function name to be retrieved from the log file.
155 #
Fred Drakef0193242001-10-12 20:56:29 +0000156 if self._loadfile(fileno):
157 filename = funcname = None
158 try:
159 filename, funcname = self._funcmap[(fileno, lineno)]
160 except KeyError:
161 filename = self._filemap.get(fileno)
162 funcname = None
163 self._funcmap[(fileno, lineno)] = (filename, funcname)
164 return filename, funcname
165
166 def _loadfile(self, fileno):
167 try:
168 filename = self._filemap[fileno]
169 except KeyError:
170 print "Could not identify fileId", fileno
171 return 1
172 if filename is None:
173 return 1
174 absname = os.path.normcase(os.path.join(self.cwd, filename))
175
176 try:
177 fp = open(absname)
178 except IOError:
179 return
180 st = parser.suite(fp.read())
181 fp.close()
182
183 # Scan the tree looking for def and lambda nodes, filling in
184 # self._funcmap with all the available information.
185 funcdef = symbol.funcdef
186 lambdef = symbol.lambdef
187
188 stack = [st.totuple(1)]
189
190 while stack:
191 tree = stack.pop()
192 try:
193 sym = tree[0]
194 except (IndexError, TypeError):
195 continue
196 if sym == funcdef:
197 self._funcmap[(fileno, tree[2][2])] = filename, tree[2][1]
198 elif sym == lambdef:
199 self._funcmap[(fileno, tree[1][2])] = filename, "<lambda>"
200 stack.extend(list(tree[1:]))