10c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport _hotshot
20c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport os.path
30c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport parser
40c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport symbol
50c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
60c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yifrom _hotshot import \
70c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi     WHAT_ENTER, \
80c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi     WHAT_EXIT, \
90c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi     WHAT_LINENO, \
100c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi     WHAT_DEFINE_FILE, \
110c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi     WHAT_DEFINE_FUNC, \
120c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi     WHAT_ADD_INFO
130c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
140c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
150c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi__all__ = ["LogReader", "ENTER", "EXIT", "LINE"]
160c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
170c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
180c5958b1636c47ed7c284f859c8e805fd06a0e6Bill YiENTER = WHAT_ENTER
190c5958b1636c47ed7c284f859c8e805fd06a0e6Bill YiEXIT  = WHAT_EXIT
200c5958b1636c47ed7c284f859c8e805fd06a0e6Bill YiLINE  = WHAT_LINENO
210c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
220c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
230c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiclass LogReader:
240c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def __init__(self, logfn):
250c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        # fileno -> filename
260c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._filemap = {}
270c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        # (fileno, lineno) -> filename, funcname
280c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._funcmap = {}
290c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
300c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._reader = _hotshot.logreader(logfn)
310c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._nextitem = self._reader.next
320c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._info = self._reader.info
330c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        if 'current-directory' in self._info:
340c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            self.cwd = self._info['current-directory']
350c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        else:
360c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            self.cwd = None
370c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
380c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        # This mirrors the call stack of the profiled code as the log
390c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        # is read back in.  It contains tuples of the form:
400c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        #
410c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        #   (file name, line number of function def, function name)
420c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        #
430c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._stack = []
440c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._append = self._stack.append
450c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._pop = self._stack.pop
460c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
470c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def close(self):
480c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._reader.close()
490c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
500c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def fileno(self):
510c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        """Return the file descriptor of the log reader's log file."""
520c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        return self._reader.fileno()
530c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
540c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def addinfo(self, key, value):
550c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        """This method is called for each additional ADD_INFO record.
560c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
570c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        This can be overridden by applications that want to receive
580c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        these events.  The default implementation does not need to be
590c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        called by alternate implementations.
600c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
610c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        The initial set of ADD_INFO records do not pass through this
620c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        mechanism; this is only needed to receive notification when
630c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        new values are added.  Subclasses can inspect self._info after
640c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        calling LogReader.__init__().
650c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        """
660c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        pass
670c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
680c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def get_filename(self, fileno):
690c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        try:
700c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            return self._filemap[fileno]
710c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        except KeyError:
720c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            raise ValueError, "unknown fileno"
730c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
740c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def get_filenames(self):
750c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        return self._filemap.values()
760c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
770c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def get_fileno(self, filename):
780c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        filename = os.path.normcase(os.path.normpath(filename))
790c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        for fileno, name in self._filemap.items():
800c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            if name == filename:
810c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                return fileno
820c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        raise ValueError, "unknown filename"
830c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
840c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def get_funcname(self, fileno, lineno):
850c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        try:
860c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            return self._funcmap[(fileno, lineno)]
870c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        except KeyError:
880c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            raise ValueError, "unknown function location"
890c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
900c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Iteration support:
910c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # This adds an optional (& ignored) parameter to next() so that the
920c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # same bound method can be used as the __getitem__() method -- this
930c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # avoids using an additional method call which kills the performance.
940c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
950c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def next(self, index=0):
960c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        while 1:
970c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            # This call may raise StopIteration:
980c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            what, tdelta, fileno, lineno = self._nextitem()
990c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1000c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            # handle the most common cases first
1010c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1020c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            if what == WHAT_ENTER:
1030c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                filename, funcname = self._decode_location(fileno, lineno)
1040c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                t = (filename, lineno, funcname)
1050c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                self._append(t)
1060c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                return what, t, tdelta
1070c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1080c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            if what == WHAT_EXIT:
1090c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                try:
1100c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    return what, self._pop(), tdelta
1110c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                except IndexError:
1120c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    raise StopIteration
1130c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1140c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            if what == WHAT_LINENO:
1150c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                filename, firstlineno, funcname = self._stack[-1]
1160c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                return what, (filename, lineno, funcname), tdelta
1170c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1180c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            if what == WHAT_DEFINE_FILE:
1190c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                filename = os.path.normcase(os.path.normpath(tdelta))
1200c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                self._filemap[fileno] = filename
1210c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            elif what == WHAT_DEFINE_FUNC:
1220c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                filename = self._filemap[fileno]
1230c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                self._funcmap[(fileno, lineno)] = (filename, tdelta)
1240c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            elif what == WHAT_ADD_INFO:
1250c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                # value already loaded into self.info; call the
1260c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                # overridable addinfo() handler so higher-level code
1270c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                # can pick up the new value
1280c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                if tdelta == 'current-directory':
1290c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    self.cwd = lineno
1300c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                self.addinfo(tdelta, lineno)
1310c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            else:
1320c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                raise ValueError, "unknown event type"
1330c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1340c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def __iter__(self):
1350c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        return self
1360c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1370c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    #
1380c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    #  helpers
1390c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    #
1400c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1410c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def _decode_location(self, fileno, lineno):
1420c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        try:
1430c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            return self._funcmap[(fileno, lineno)]
1440c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        except KeyError:
1450c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            #
1460c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            # This should only be needed when the log file does not
1470c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            # contain all the DEFINE_FUNC records needed to allow the
1480c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            # function name to be retrieved from the log file.
1490c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            #
1500c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            if self._loadfile(fileno):
1510c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                filename = funcname = None
1520c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            try:
1530c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                filename, funcname = self._funcmap[(fileno, lineno)]
1540c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            except KeyError:
1550c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                filename = self._filemap.get(fileno)
1560c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                funcname = None
1570c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                self._funcmap[(fileno, lineno)] = (filename, funcname)
1580c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        return filename, funcname
1590c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1600c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def _loadfile(self, fileno):
1610c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        try:
1620c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            filename = self._filemap[fileno]
1630c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        except KeyError:
1640c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            print "Could not identify fileId", fileno
1650c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            return 1
1660c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        if filename is None:
1670c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            return 1
1680c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        absname = os.path.normcase(os.path.join(self.cwd, filename))
1690c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1700c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        try:
1710c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            fp = open(absname)
1720c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        except IOError:
1730c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            return
1740c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        st = parser.suite(fp.read())
1750c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        fp.close()
1760c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1770c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        # Scan the tree looking for def and lambda nodes, filling in
1780c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        # self._funcmap with all the available information.
1790c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        funcdef = symbol.funcdef
1800c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        lambdef = symbol.lambdef
1810c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1820c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        stack = [st.totuple(1)]
1830c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1840c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        while stack:
1850c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            tree = stack.pop()
1860c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            try:
1870c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                sym = tree[0]
1880c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            except (IndexError, TypeError):
1890c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                continue
1900c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            if sym == funcdef:
1910c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                self._funcmap[(fileno, tree[2][2])] = filename, tree[2][1]
1920c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            elif sym == lambdef:
1930c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                self._funcmap[(fileno, tree[1][2])] = filename, "<lambda>"
1940c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            stack.extend(list(tree[1:]))
195