1import _hotshot
2import os.path
3import parser
4import symbol
5
6from _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
18ENTER = WHAT_ENTER
19EXIT  = WHAT_EXIT
20LINE  = WHAT_LINENO
21
22
23class 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 'current-directory' in self._info:
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                try:
110                    return what, self._pop(), tdelta
111                except IndexError:
112                    raise StopIteration
113
114            if what == WHAT_LINENO:
115                filename, firstlineno, funcname = self._stack[-1]
116                return what, (filename, lineno, funcname), tdelta
117
118            if what == WHAT_DEFINE_FILE:
119                filename = os.path.normcase(os.path.normpath(tdelta))
120                self._filemap[fileno] = filename
121            elif what == WHAT_DEFINE_FUNC:
122                filename = self._filemap[fileno]
123                self._funcmap[(fileno, lineno)] = (filename, tdelta)
124            elif what == WHAT_ADD_INFO:
125                # value already loaded into self.info; call the
126                # overridable addinfo() handler so higher-level code
127                # can pick up the new value
128                if tdelta == 'current-directory':
129                    self.cwd = lineno
130                self.addinfo(tdelta, lineno)
131            else:
132                raise ValueError, "unknown event type"
133
134    def __iter__(self):
135        return self
136
137    #
138    #  helpers
139    #
140
141    def _decode_location(self, fileno, lineno):
142        try:
143            return self._funcmap[(fileno, lineno)]
144        except KeyError:
145            #
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.
149            #
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
159
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))
169
170        try:
171            fp = open(absname)
172        except IOError:
173            return
174        st = parser.suite(fp.read())
175        fp.close()
176
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
181
182        stack = [st.totuple(1)]
183
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:]))
195