Home | History | Annotate | Download | only in hotshot
      1 import _hotshot
      2 import os.path
      3 import parser
      4 import symbol
      5 
      6 from _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 
     18 ENTER = WHAT_ENTER
     19 EXIT  = WHAT_EXIT
     20 LINE  = WHAT_LINENO
     21 
     22 
     23 class 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