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