"""CherryPy browser for PyConquer logs. Use PyConquer to gether logs, then use the serve() function to browse the results in a web browser. If you run this module from the command line, it will call serve() for you. """ import cgi import os import re import sys try: import cStringIO as StringIO except ImportError: import StringIO import urllib import cherrypy TEMPLATE_INDEX = """ PyConquer Logs """ TEMPLATE_MENU = """ PyConquer Menu

PyConquer Logs

Click on one of the runs below to see pyconquer data.

%s """ TEMPLATE_REPORT = r""" %(filename)s


%s %s (%s:%s)
%s %s (%s:%s)
""" class Event(object): def __init__(self, line, pos): self.pos = pos self.result = None self.time = None m = re.match(r"(?P-+)(?P.) import " r"(?P.*)( \(\d lines\))?", line) if m is not None: indent, msgtype, module, _ = m.groups() self.func = "import" self.lineno = 0 else: m = re.match(r"(?P-+)(?P.) (?P.*)" r" \((?P.*):(?P\d+)\)(?P.*)", line) indent, msgtype, func, module, lineno, result = m.groups() if result.endswith("ms"): result, time = result.rsplit(" ", 1) self.time = float(time[:-2]) self.func = func self.lineno = lineno self.result = result or None self.indent = len(indent) self.msgtype = msgtype self.module = module class LogBrowser(object): def __init__(self, path): self.path = os.path.abspath(path) def index(self): return TEMPLATE_INDEX index.exposed = True def menu(self): runs = [f for f in os.listdir(self.path) if f.startswith("pyconquer") and f.endswith(".log")] runs.sort() return TEMPLATE_MENU % '\n'.join([ "%s
" % (i, i) for i in runs]) menu.exposed = True def call_content(self, filename, pos): """Return all lines one indent depth inside the given call. pos: the 0-based index for the line where the call block begins (i.e. the line of the call itself). """ pos = int(pos) lines = open(filename, 'rb').readlines()[pos:] if not lines: return [] content = [] call_line = lines.pop(0).strip() if call_line.startswith('--'): indent0 = Event(call_line, 0).indent else: indent0 = 0 for line in lines: pos += 1 line = line.strip() if line.startswith('--'): e = Event(line, pos) if e.indent <= indent0: break else: content.append(e) if not content: return [] min_depth = min([event.indent for event in content]) return [event for event in content if event.indent == min_depth] def callblock(self, filename, pos): fullpath = os.path.abspath(os.path.join(self.path, filename)) if not fullpath.startswith(self.path): raise cherrypy.HTTPError(403) results = [] for e in self.call_content(fullpath, pos): typename = {'>': 'call', '<': 'return', '[': 'ccall', ']': 'creturn', 'E': 'exception', 'e': 'cexception', '.': 'line', '=': 'watch', 'X': 'threadexit', '*': 'threadstart'}[e.msgtype] if e.time is None: time = '' else: time = "%0.3f" % e.time if typename in ('exception', 'cexception', 'call', 'ccall'): results.append(TEMPLATE_CALL % (typename, time, e.msgtype, e.pos, e.func, e.module, e.lineno)) else: results.append(TEMPLATE_LINE % (typename, time, e.msgtype, e.func, e.module, e.lineno)) return ''.join(results) callblock.exposed = True def report(self, filename): return TEMPLATE_REPORT % {'filename': filename, 'content': self.callblock(filename, 0)} report.exposed = True def serve(port=8080, path=os.getcwd()): import cherrypy cherrypy.config.update({'server.socket_port': int(port), 'server.thread_pool': 10, }) cherrypy.quickstart(LogBrowser(path)) if __name__ == "__main__": serve(*tuple(sys.argv[1:]))