14710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport _hotshot
24710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport os.path
34710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport parser
44710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport symbol
54710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
64710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmfrom _hotshot import \
74710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     WHAT_ENTER, \
84710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     WHAT_EXIT, \
94710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     WHAT_LINENO, \
104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     WHAT_DEFINE_FILE, \
114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     WHAT_DEFINE_FUNC, \
124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     WHAT_ADD_INFO
134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm__all__ = ["LogReader", "ENTER", "EXIT", "LINE"]
164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmENTER = WHAT_ENTER
194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmEXIT  = WHAT_EXIT
204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmLINE  = WHAT_LINENO
214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmclass LogReader:
244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __init__(self, logfn):
254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # fileno -> filename
264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._filemap = {}
274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # (fileno, lineno) -> filename, funcname
284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._funcmap = {}
294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._reader = _hotshot.logreader(logfn)
314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._nextitem = self._reader.next
324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._info = self._reader.info
334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if 'current-directory' in self._info:
344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.cwd = self._info['current-directory']
354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        else:
364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.cwd = None
374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # This mirrors the call stack of the profiled code as the log
394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # is read back in.  It contains tuples of the form:
404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        #
414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        #   (file name, line number of function def, function name)
424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        #
434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._stack = []
444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._append = self._stack.append
454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._pop = self._stack.pop
464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def close(self):
484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._reader.close()
494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def fileno(self):
514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Return the file descriptor of the log reader's log file."""
524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._reader.fileno()
534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def addinfo(self, key, value):
554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """This method is called for each additional ADD_INFO record.
564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        This can be overridden by applications that want to receive
584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        these events.  The default implementation does not need to be
594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        called by alternate implementations.
604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        The initial set of ADD_INFO records do not pass through this
624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        mechanism; this is only needed to receive notification when
634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        new values are added.  Subclasses can inspect self._info after
644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        calling LogReader.__init__().
654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        pass
674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def get_filename(self, fileno):
694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return self._filemap[fileno]
714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except KeyError:
724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            raise ValueError, "unknown fileno"
734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def get_filenames(self):
754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._filemap.values()
764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def get_fileno(self, filename):
784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        filename = os.path.normcase(os.path.normpath(filename))
794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for fileno, name in self._filemap.items():
804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if name == filename:
814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return fileno
824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        raise ValueError, "unknown filename"
834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def get_funcname(self, fileno, lineno):
854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return self._funcmap[(fileno, lineno)]
874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except KeyError:
884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            raise ValueError, "unknown function location"
894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Iteration support:
914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # This adds an optional (& ignored) parameter to next() so that the
924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # same bound method can be used as the __getitem__() method -- this
934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # avoids using an additional method call which kills the performance.
944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def next(self, index=0):
964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while 1:
974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # This call may raise StopIteration:
984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            what, tdelta, fileno, lineno = self._nextitem()
994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # handle the most common cases first
1014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if what == WHAT_ENTER:
1034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                filename, funcname = self._decode_location(fileno, lineno)
1044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                t = (filename, lineno, funcname)
1054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self._append(t)
1064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return what, t, tdelta
1074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if what == WHAT_EXIT:
1094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                try:
1104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    return what, self._pop(), tdelta
1114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                except IndexError:
1124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    raise StopIteration
1134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if what == WHAT_LINENO:
1154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                filename, firstlineno, funcname = self._stack[-1]
1164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return what, (filename, lineno, funcname), tdelta
1174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if what == WHAT_DEFINE_FILE:
1194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                filename = os.path.normcase(os.path.normpath(tdelta))
1204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self._filemap[fileno] = filename
1214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif what == WHAT_DEFINE_FUNC:
1224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                filename = self._filemap[fileno]
1234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self._funcmap[(fileno, lineno)] = (filename, tdelta)
1244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif what == WHAT_ADD_INFO:
1254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                # value already loaded into self.info; call the
1264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                # overridable addinfo() handler so higher-level code
1274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                # can pick up the new value
1284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                if tdelta == 'current-directory':
1294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    self.cwd = lineno
1304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.addinfo(tdelta, lineno)
1314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
1324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                raise ValueError, "unknown event type"
1334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __iter__(self):
1354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self
1364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    #
1384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    #  helpers
1394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    #
1404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _decode_location(self, fileno, lineno):
1424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
1434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return self._funcmap[(fileno, lineno)]
1444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except KeyError:
1454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            #
1464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # This should only be needed when the log file does not
1474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # contain all the DEFINE_FUNC records needed to allow the
1484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # function name to be retrieved from the log file.
1494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            #
1504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if self._loadfile(fileno):
1514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                filename = funcname = None
1524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            try:
1534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                filename, funcname = self._funcmap[(fileno, lineno)]
1544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            except KeyError:
1554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                filename = self._filemap.get(fileno)
1564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                funcname = None
1574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self._funcmap[(fileno, lineno)] = (filename, funcname)
1584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return filename, funcname
1594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _loadfile(self, fileno):
1614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
1624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            filename = self._filemap[fileno]
1634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except KeyError:
1644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print "Could not identify fileId", fileno
1654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return 1
1664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if filename is None:
1674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return 1
1684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        absname = os.path.normcase(os.path.join(self.cwd, filename))
1694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
1714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            fp = open(absname)
1724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except IOError:
1734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return
1744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        st = parser.suite(fp.read())
1754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        fp.close()
1764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Scan the tree looking for def and lambda nodes, filling in
1784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # self._funcmap with all the available information.
1794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        funcdef = symbol.funcdef
1804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        lambdef = symbol.lambdef
1814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        stack = [st.totuple(1)]
1834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while stack:
1854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            tree = stack.pop()
1864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            try:
1874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                sym = tree[0]
1884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            except (IndexError, TypeError):
1894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                continue
1904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if sym == funcdef:
1914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self._funcmap[(fileno, tree[2][2])] = filename, tree[2][1]
1924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif sym == lambdef:
1934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self._funcmap[(fileno, tree[1][2])] = filename, "<lambda>"
1944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            stack.extend(list(tree[1:]))
195