"""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
%(filename)s
%(content)s
"""
TEMPLATE_LINE = """
"""
TEMPLATE_CALL = """
"""
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:]))