14adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoimport _hotshot
24adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoimport os.path
34adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoimport parser
44adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoimport symbol
54adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
64adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaofrom _hotshot import \
74adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao     WHAT_ENTER, \
84adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao     WHAT_EXIT, \
94adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao     WHAT_LINENO, \
104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao     WHAT_DEFINE_FILE, \
114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao     WHAT_DEFINE_FUNC, \
124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao     WHAT_ADD_INFO
134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao__all__ = ["LogReader", "ENTER", "EXIT", "LINE"]
164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
184adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoENTER = WHAT_ENTER
194adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoEXIT  = WHAT_EXIT
204adfde8bc82dd39f59e0445588c3e599ada477dJosh GaoLINE  = WHAT_LINENO
214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gaoclass LogReader:
244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def __init__(self, logfn):
254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        # fileno -> filename
264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self._filemap = {}
274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        # (fileno, lineno) -> filename, funcname
284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self._funcmap = {}
294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self._reader = _hotshot.logreader(logfn)
314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self._nextitem = self._reader.next
324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self._info = self._reader.info
334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if 'current-directory' in self._info:
344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.cwd = self._info['current-directory']
354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        else:
364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            self.cwd = None
374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        # This mirrors the call stack of the profiled code as the log
394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        # is read back in.  It contains tuples of the form:
404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        #
414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        #   (file name, line number of function def, function name)
424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        #
434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self._stack = []
444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self._append = self._stack.append
454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self._pop = self._stack.pop
464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def close(self):
484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        self._reader.close()
494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def fileno(self):
514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        """Return the file descriptor of the log reader's log file."""
524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        return self._reader.fileno()
534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def addinfo(self, key, value):
554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        """This method is called for each additional ADD_INFO record.
564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        This can be overridden by applications that want to receive
584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        these events.  The default implementation does not need to be
594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        called by alternate implementations.
604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        The initial set of ADD_INFO records do not pass through this
624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        mechanism; this is only needed to receive notification when
634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        new values are added.  Subclasses can inspect self._info after
644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        calling LogReader.__init__().
654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        """
664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        pass
674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def get_filename(self, fileno):
694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        try:
704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            return self._filemap[fileno]
714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        except KeyError:
724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            raise ValueError, "unknown fileno"
734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def get_filenames(self):
754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        return self._filemap.values()
764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def get_fileno(self, filename):
784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        filename = os.path.normcase(os.path.normpath(filename))
794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        for fileno, name in self._filemap.items():
804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if name == filename:
814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                return fileno
824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        raise ValueError, "unknown filename"
834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def get_funcname(self, fileno, lineno):
854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        try:
864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            return self._funcmap[(fileno, lineno)]
874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        except KeyError:
884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            raise ValueError, "unknown function location"
894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    # Iteration support:
914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    # This adds an optional (& ignored) parameter to next() so that the
924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    # same bound method can be used as the __getitem__() method -- this
934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    # avoids using an additional method call which kills the performance.
944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
954adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def next(self, index=0):
964adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        while 1:
974adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            # This call may raise StopIteration:
984adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            what, tdelta, fileno, lineno = self._nextitem()
994adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1004adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            # handle the most common cases first
1014adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1024adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if what == WHAT_ENTER:
1034adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                filename, funcname = self._decode_location(fileno, lineno)
1044adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                t = (filename, lineno, funcname)
1054adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                self._append(t)
1064adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                return what, t, tdelta
1074adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1084adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if what == WHAT_EXIT:
1094adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                try:
1104adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    return what, self._pop(), tdelta
1114adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                except IndexError:
1124adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    raise StopIteration
1134adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1144adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if what == WHAT_LINENO:
1154adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                filename, firstlineno, funcname = self._stack[-1]
1164adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                return what, (filename, lineno, funcname), tdelta
1174adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1184adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if what == WHAT_DEFINE_FILE:
1194adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                filename = os.path.normcase(os.path.normpath(tdelta))
1204adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                self._filemap[fileno] = filename
1214adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            elif what == WHAT_DEFINE_FUNC:
1224adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                filename = self._filemap[fileno]
1234adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                self._funcmap[(fileno, lineno)] = (filename, tdelta)
1244adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            elif what == WHAT_ADD_INFO:
1254adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                # value already loaded into self.info; call the
1264adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                # overridable addinfo() handler so higher-level code
1274adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                # can pick up the new value
1284adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                if tdelta == 'current-directory':
1294adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                    self.cwd = lineno
1304adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                self.addinfo(tdelta, lineno)
1314adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            else:
1324adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                raise ValueError, "unknown event type"
1334adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1344adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def __iter__(self):
1354adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        return self
1364adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1374adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    #
1384adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    #  helpers
1394adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    #
1404adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1414adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def _decode_location(self, fileno, lineno):
1424adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        try:
1434adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            return self._funcmap[(fileno, lineno)]
1444adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        except KeyError:
1454adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            #
1464adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            # This should only be needed when the log file does not
1474adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            # contain all the DEFINE_FUNC records needed to allow the
1484adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            # function name to be retrieved from the log file.
1494adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            #
1504adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if self._loadfile(fileno):
1514adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                filename = funcname = None
1524adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            try:
1534adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                filename, funcname = self._funcmap[(fileno, lineno)]
1544adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            except KeyError:
1554adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                filename = self._filemap.get(fileno)
1564adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                funcname = None
1574adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                self._funcmap[(fileno, lineno)] = (filename, funcname)
1584adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        return filename, funcname
1594adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1604adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao    def _loadfile(self, fileno):
1614adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        try:
1624adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            filename = self._filemap[fileno]
1634adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        except KeyError:
1644adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            print "Could not identify fileId", fileno
1654adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            return 1
1664adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        if filename is None:
1674adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            return 1
1684adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        absname = os.path.normcase(os.path.join(self.cwd, filename))
1694adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1704adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        try:
1714adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            fp = open(absname)
1724adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        except IOError:
1734adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            return
1744adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        st = parser.suite(fp.read())
1754adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        fp.close()
1764adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1774adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        # Scan the tree looking for def and lambda nodes, filling in
1784adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        # self._funcmap with all the available information.
1794adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        funcdef = symbol.funcdef
1804adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        lambdef = symbol.lambdef
1814adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1824adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        stack = [st.totuple(1)]
1834adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao
1844adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao        while stack:
1854adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            tree = stack.pop()
1864adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            try:
1874adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                sym = tree[0]
1884adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            except (IndexError, TypeError):
1894adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                continue
1904adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            if sym == funcdef:
1914adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                self._funcmap[(fileno, tree[2][2])] = filename, tree[2][1]
1924adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            elif sym == lambdef:
1934adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao                self._funcmap[(fileno, tree[1][2])] = filename, "<lambda>"
1944adfde8bc82dd39f59e0445588c3e599ada477dJosh Gao            stack.extend(list(tree[1:]))
195