Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2023-03-17 10:57:38

0001 #!/usr/bin/env python3
0002 from __future__ import print_function
0003 import os
0004 import re
0005 import sys
0006 import inspect
0007 import sqlite3
0008 import tempfile
0009 import traceback
0010 import subprocess
0011 from shutil import copy, rmtree
0012 from collections import defaultdict
0013 
0014 ### 1. Some configuration options.
0015 SERVE_PORT = 1234
0016 
0017 OUTFILE_TREE = "configtree.sqlite"
0018 IGNORE_PACKAGES = ['FWCore/ParameterSet', 'DQMOffline/Configuration/scripts', "cmsRun"]
0019 STRIPPATHS = [ # we will add the base dir from CMSSWCALLBASE env var here
0020   os.environ["CMSSW_BASE"] + "/python/", os.environ["CMSSW_RELEASE_BASE"] + "/python/",
0021   os.environ["CMSSW_BASE"] + "/cfipython/", os.environ["CMSSW_RELEASE_BASE"] + "/cfipython/"]
0022 PREFIXINFO = [] # what we will show as "source" later
0023 ARGV0 = "" # set in main
0024 
0025 ### 2. The enable all the tracing hooks.
0026 
0027 # this already does a good job, but it is not enough
0028 #import FWCore.GuiBrowsers.EnablePSetHistory
0029 
0030 # we need to patch out this to allow printing unlabled things
0031 import FWCore.ParameterSet.Mixins
0032 del FWCore.ParameterSet.Mixins._Labelable.__str__
0033 
0034 # then also trace Sequence construction so we can get a full tree
0035 # (PSetHistory only tracks leaves)
0036 def auto_inspect():
0037   # stolen from FWCore.GuiBrowsers.EnablePSetHistory, but needs more black-list
0038   stack = inspect.stack()
0039   i = 0
0040   while i < len(stack) and len(stack[i])>=2 and any(map(lambda p: p in stack[i][1], IGNORE_PACKAGES)):
0041     i += 1
0042   res = stack[i: ]
0043   j = 0
0044   # for the other end we only use cmsRun (instead of IGNORE_PACKAGES) to avoid
0045   # cutting traces in the middle that enter and leave IGNORE_PACKAGES
0046   while j < len(res) and not 'cmsRun' in res[j][1]:
0047     j += 1
0048   res = res[:j]
0049   if len(res)>=1 and len(res[0])>=3:
0050     return res
0051   else:
0052     return [("unknown","unknown","unknown")]
0053 
0054 def trace_location(thing, name, extra = lambda thing, *args, **kwargs: thing):
0055   old_method = getattr(thing, name)
0056   def trace_location_hook(self, *args, **kwargs):
0057     retval = old_method(self, *args, **kwargs)
0058     where = auto_inspect()
0059     #print("Called %s::%s at %s" % (thing.__name__, name, where[0][1:3]))
0060     event = (name, tuple(w[1:3] for w in where), extra(self, *args, **kwargs))
0061     if hasattr(self, "_trace_events"):
0062       getattr(self, "_trace_events").append(event)
0063     else:
0064       # this bypasses setattr checks
0065       self.__dict__["_trace_events"] = [ event ]
0066 
0067     return retval
0068   setattr(thing, name, trace_location_hook)
0069 
0070 def flatten(*args):
0071   # that was surprisingly hard...
0072   return     [x  for x in args if not isinstance(x, list)] + sum(
0073     [flatten(*x) for x in args if isinstance(x, list)], [])
0074 
0075 from FWCore.ParameterSet.SequenceTypes import _ModuleSequenceType, _SequenceCollection, Task, _UnarySequenceOperator, Schedule
0076 from FWCore.ParameterSet.Modules import _Module, Source, ESSource, ESPrefer, ESProducer, Service, Looper
0077 from FWCore.ParameterSet.Config import Process
0078 # with this we can also track the '+' and '*' of modules, but it is slow
0079 trace_location(_SequenceCollection, '__init__')
0080 trace_location(_Module, '__init__')
0081 trace_location(Source, '__init__')
0082 trace_location(ESSource, '__init__')
0083 trace_location(ESPrefer, '__init__')
0084 trace_location(ESProducer, '__init__')
0085 trace_location(Service, '__init__')
0086 trace_location(Looper, '__init__')
0087 trace_location(Process, '__init__')
0088 trace_location(_UnarySequenceOperator, '__init__')
0089 # lambda agrument names all match the original declarations, to make kwargs work
0090 trace_location(_ModuleSequenceType, '__init__', lambda self, *arg: {'args': list(arg)})
0091 trace_location(_ModuleSequenceType, 'copy')
0092 trace_location(_ModuleSequenceType, 'associate', lambda self, *tasks: {'args': list(tasks)})
0093 trace_location(_ModuleSequenceType, '__imul__', lambda self, rhs: {'rhs': rhs})
0094 trace_location(_ModuleSequenceType, '__iadd__', lambda self, rhs: {'rhs': rhs})
0095 trace_location(_ModuleSequenceType, 'copyAndExclude', lambda self, listOfModulesToExclude: {'olds': list(listOfModulesToExclude)})
0096 trace_location(_ModuleSequenceType, 'replace', lambda self, original, replacement: {'old': original, 'new': replacement})
0097 trace_location(_ModuleSequenceType, 'insert', lambda self, index, item: {'rhs': item})
0098 trace_location(_ModuleSequenceType, 'remove', lambda self, something: {'old': something})
0099 trace_location(Task, '__init__')
0100 trace_location(Task, 'add', lambda self, *items: {'args': list(items)})
0101 trace_location(Task, 'copy')
0102 trace_location(Task, 'copyAndExclude', lambda self, listOfModulesToExclude: {'olds': list(listOfModulesToExclude)})
0103 trace_location(Schedule, '__init__', lambda self, *args, **kwargs: {'args': flatten(list(args), kwargs.values())})
0104 trace_location(Schedule, 'associate', lambda self, *tasks: {'args': list(tasks)})
0105 trace_location(Schedule, 'copy')
0106 # TODO: we could go deeper into Types and PSet, but that should not be needed for now.
0107 
0108 # lifted from EnablePSetHistory, we don't need all of that stuff.
0109 def new_items_(self):
0110   items = []
0111   if self.source:
0112     items += [("source", self.source)]
0113   if self.looper:
0114     items += [("looper", self.looper)]
0115   #items += self.moduleItems_()
0116   items += self.outputModules.items()
0117   #items += self.sequences.items() # TODO: we don't need sequences that are not paths?
0118   items += self.paths.items()
0119   items += self.endpaths.items()
0120   items += self.services.items()
0121   items += self.es_producers.items()
0122   items += self.es_sources.items()
0123   items += self.es_prefers.items()
0124   #items += self.psets.items()
0125   #items += self.vpsets.items()
0126   if self.schedule:
0127     items += [("schedule", self.schedule)]
0128   return tuple(items)
0129 Process.items_=new_items_
0130 
0131 ### 3. The logic to collect, process, and save the information from the process.
0132 
0133 def collect_trace(thing, name, graph, parent):
0134   # thing is what to look at, graph is the output list (of child, parent tuple pairs)
0135   # thing could be pretty much anything.
0136   classname = thing.__class__.__name__
0137   if hasattr(thing, '_trace_events'):
0138     events = list(getattr(thing, '_trace_events'))
0139     getattr(thing, '_trace_events')[:] = [] # erase events so we can't end up in cycles
0140     for action, loc, extra in events:
0141       entry = (action, classname, loc)
0142       graph.append((entry, parent, name))
0143 
0144       # items shall be a list of tuples (type, object) of the immediate children of the thing.
0145       items = []
0146       if hasattr(extra, 'items_'): # for cms.Process
0147         items += extra.items_()
0148       if hasattr(extra, '_seq'): # for sequences and similar
0149         seq = getattr(extra, '_seq')
0150         if seq:
0151           items += [('seqitem', x) for x in getattr(seq, '_collection')]
0152       if hasattr(extra, '_tasks'): # same
0153         items += [('task', x) for x in getattr(extra, '_tasks')]
0154       if hasattr(extra, '_collection'): # for cms.Task
0155         items += [('subtask', x) for x in getattr(extra, '_collection')]
0156       if hasattr(extra, '_operand'): # for _SeqenceNegation etc.
0157         items += [('operand', getattr(extra, '_operand'))]
0158       if isinstance(extra, dict): # stuff that we track explicitly^
0159         for key, value in extra.items():
0160           if isinstance(value, list):
0161             items += [(key, x) for x in value]
0162           else:
0163             items += [(key, value)]
0164 
0165       for name, child in items:
0166         collect_trace(child, name, graph, entry)
0167 
0168   else:
0169     if not thing is None:
0170       print("No _trace_events found in %s.\nMaybe turn on tracing for %s?" % (thing, classname))
0171       print(" Found in %s" % (parent,))
0172 
0173 
0174 def writeoutput(graph):
0175   progname = " ".join(PREFIXINFO)
0176   print("+Done running %s, writing output..." % progname)
0177 
0178   def formatfile(filename):
0179     filename = os.path.abspath(filename)
0180     for pfx in STRIPPATHS:
0181       if filename.startswith(pfx):
0182         filename = filename[len(pfx):]
0183     return filename
0184 
0185   files = set()
0186   for child, parent, relation in graph:
0187     files.add(child[2][0][0])
0188     files.add(parent[2][0][0])
0189 
0190   conn = sqlite3.connect(os.environ["CMSSWCALLTREE"])
0191   cur = conn.cursor()
0192   cur.executescript("""
0193   CREATE TABLE IF NOT EXISTS file(id INTEGER PRIMARY KEY, 
0194     name TEXT, UNIQUE(name)
0195   );
0196   CREATE TABLE IF NOT EXISTS trace(id INTEGER PRIMARY KEY, 
0197     parent INTEGER, -- points into same table, recursively
0198     file INTEGER, line INTEGER,
0199     FOREIGN KEY(parent) REFERENCES trace(id),
0200     FOREIGN KEY(file) REFERENCES file(id),
0201     UNIQUE(parent, file, line)
0202   );
0203   CREATE TABLE IF NOT EXISTS relation(id INTEGER PRIMARY KEY,
0204     place  INTEGER, 
0205     place_type TEXT,
0206     usedby INTEGER, 
0207     usedby_type TEXT,
0208     relation TEXT,
0209     source TEXT,
0210     FOREIGN KEY(place) REFERENCES trace(id),
0211     FOREIGN KEY(usedby) REFERENCES trace(id)
0212   );
0213   CREATE INDEX IF NOT EXISTS placeidx ON relation(place);
0214   CREATE INDEX IF NOT EXISTS usedbyidx ON relation(usedby);
0215   CREATE INDEX IF NOT EXISTS traceidx ON trace(file);
0216   -- SQLite does not optimise that one well, but a VIEW is still nice to have...
0217   CREATE VIEW IF NOT EXISTS fulltrace AS 
0218     WITH RECURSIVE fulltrace(level, baseid, parent, file, name, line) AS (
0219       SELECT 1 AS level, trace.id, parent, trace.file, file.name, line FROM trace
0220         INNER JOIN file ON file.id = trace.file
0221       UNION SELECT level+1, baseid, trace.parent, trace.file, file.name, trace.line FROM fulltrace 
0222         INNER JOIN trace ON trace.id = fulltrace.parent
0223         INNER JOIN file ON file.id = trace.file)
0224     SELECT * FROM fulltrace;
0225   """)
0226   cur.executemany("INSERT OR IGNORE INTO file(name) VALUES (?);", 
0227     ((formatfile(f),) for f in files))
0228   def inserttrace(loc):
0229     parent = 0
0230     for filename, line in reversed(loc):
0231       conn.execute("INSERT OR IGNORE INTO trace(parent, file, line) SELECT ?, id, ? FROM file WHERE name == ?;", (parent, line, formatfile(filename)))
0232       cur = conn.execute("SELECT trace.id FROM trace LEFT JOIN file ON trace.file == file.id WHERE trace.parent = ? AND file.name = ? AND trace.line = ?;", (parent, formatfile(filename), line))
0233       for row in cur:
0234         parent = row[0]
0235     return parent
0236 
0237   for child, parent, relation in graph:
0238     cevt, cclassname, cloc = child
0239     pevt, pclassname, ploc = parent
0240     place = inserttrace(cloc)
0241     usedby = inserttrace(ploc)
0242     cur.execute("INSERT OR IGNORE INTO relation(place, place_type, usedby, usedby_type, relation, source) VALUES (?,?,?,?,?,?);", (
0243       place, "%s::%s" % (cclassname, cevt),
0244       usedby, "%s::%s" % (pclassname, pevt),
0245       relation, progname
0246     ))
0247   conn.commit()
0248   conn.close()
0249 
0250 ### 4. Launch and keep track of all the processes.
0251 
0252 def addprefixinfo(argv):
0253   cwd = os.path.abspath(os.getcwd())
0254   wf = re.match(".*/(\d+\.\d+)_", cwd)
0255   if wf: 
0256     PREFIXINFO.append("wf")
0257     PREFIXINFO.append(wf.groups()[0])
0258   online = re.match("(.*/)?(.*)_dqm_sourceclient-live_cfg\.py", argv[0])
0259   if online:
0260     PREFIXINFO.append("online")
0261     PREFIXINFO.append(online.groups()[1])
0262   step = re.match("(step\d+)_.*\.py", argv[0])
0263   if step:
0264     PREFIXINFO.append(step.groups()[0])
0265   processing = re.match("step\d+_.*(RECO|ALCA|HARVEST).*\.py", argv[0])
0266   if processing:
0267     PREFIXINFO.append(processing.groups()[0])
0268   if not PREFIXINFO:
0269     PREFIXINFO.append(argv[0])
0270 
0271 def setupenv():
0272   bindir = tempfile.mkdtemp()
0273   print("+Setting up in ", bindir)
0274   os.symlink(ARGV0, bindir + "/cmsRun")
0275   os.environ["PATH"] = bindir + ":" + os.environ["PATH"]
0276   os.environ["CMSSWCALLTREE"] = bindir + "/" + OUTFILE_TREE
0277   os.environ["CMSSWCALLBASE"] = os.path.abspath(os.getcwd()) + "/"
0278   with open(os.environ["CMSSWCALLTREE"], "w") as f:
0279     pass
0280   return bindir
0281 
0282 def cleanupenv(tmpdir):
0283   print("+Cleaning up ", tmpdir)
0284   copy(os.environ["CMSSWCALLTREE"], ".")
0285   rmtree(tmpdir)
0286 
0287 
0288 def trace_command(argv):
0289   tmpdir = None
0290   if not "CMSSWCALLTREE" in os.environ:
0291     tmpdir = setupenv()
0292 
0293   subprocess.call(argv)
0294 
0295   if tmpdir:
0296     cleanupenv(tmpdir)
0297 
0298 def trace_python(prog_argv, path):
0299   graph = []
0300   sys.argv = prog_argv
0301   progname = prog_argv[0]
0302   file_path = searchinpath(progname, path)
0303   try:
0304     with open(file_path) as fp:
0305       code = compile(fp.read(), progname, 'exec')
0306       globals = {}
0307       try:
0308         exec code in globals, globals
0309       except:
0310         print(traceback.format_exc())
0311       finally:
0312         # reporting is only possible if the config was executed successfully.
0313         # we still do it in case of an exception, which can happen after convertToUnscheduled()
0314         print("+Collecting trace information from %s..." % globals["process"])
0315         collect_trace(globals["process"], 'cmsrun', graph, ('cmsRun', '', ((progname, 0),)))
0316         writeoutput(graph)
0317 
0318   except OSError as err:
0319     print("+Cannot run file %r because: %s" % (sys.argv[0], err))
0320     sys.exit(1)
0321   except SystemExit:
0322     pass
0323   # this is not necessarily reached at all. 
0324   sys.exit(0)
0325 
0326 def searchinpath(progname, path):
0327   # Search $PATH. There seems to be no pre-made function for this.
0328   for entry in path:
0329     file_path = os.path.join(entry, progname)
0330     if os.path.isfile(file_path):
0331       break
0332   if not os.path.isfile(file_path):
0333     print("+Cannot find program (%s) in modified $PATH (%s)." % (progname, path))
0334     sys.exit(1)
0335   print("+Found %s as %s in %s." % (progname, file_path, path))
0336   return file_path
0337 
0338 
0339 def help():
0340   print("Usage: %s <some cmssw commandline>" % (sys.argv[0]))
0341   print("  The given programs will be executed, instrumenting calls to cmsRun.")
0342   print("  cmsRun will not actually run cmssw, but all the Python code will be executed and instrumentd. The results are written to the file `%s` in the same directory." % OUTFILE_TREE)
0343   print("  The callgraph output lists edges pointing from each function to the one calling it.")
0344   print("  To view the results using a simple webpage, use\n   %s serve" % sys.argv[0])
0345   print("Examples:")
0346   print("  %s runTheMatrix.py -l 1000 --ibeos" % sys.argv[0])
0347 
0348 def main():
0349   print("+Running cmsswConfigtrace...")
0350   global ARGV0
0351   ARGV0 = sys.argv[0]
0352   if sys.argv[0].endswith('cmsRun'):
0353       print("+Wrapping cmsRun...")
0354       addprefixinfo(sys.argv[1:])
0355       STRIPPATHS.append(os.environ["CMSSWCALLBASE"])
0356       trace_python(sys.argv[1:], ["."])
0357       return
0358   if len(sys.argv) <= 1:
0359     help()
0360     return
0361   # else
0362   print("+Running command with tracing %s..." % sys.argv[1:])
0363   trace_command(sys.argv[1:])
0364 
0365 ### 5. The web-based GUI.
0366 
0367 def serve_main():
0368   # we use STRIPPATHS as a search path to find code files.
0369   STRIPPATHS.append(os.path.abspath(os.getcwd()) + "/")
0370 
0371   import SimpleHTTPServer
0372   import SocketServer
0373   import urllib 
0374 
0375   conn = sqlite3.connect(OUTFILE_TREE)
0376 
0377   def escape(s):
0378     return str(s).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
0379 
0380   def formatsource(source, formatstr = '<em class="%s">%s</em>'):
0381     processings = ["ALCA", "RECO", "HARVEST", "online"]
0382     info = source.split(" ")
0383     processing = " ".join(filter(lambda x: x in processings, info))
0384     source = " ".join(filter(lambda x: x not in processings, info))
0385     return formatstr % (processing, escape(source))
0386 
0387   def formatplace(filename, line):
0388     MAXLEN = 80
0389     shortname = filename[ :(MAXLEN-3)/2] + "..." + filename[-(MAXLEN-3)/2: ] if len(filename) > MAXLEN else filename
0390     return '<a href="/%s#%s" target="_blank">%s:%s</a>' % (escape(filename), line, escape(shortname), line)
0391 
0392   def index():
0393     out = [escape('goto to /<filename> for info about a file')]
0394 
0395     wfs = defaultdict(list)
0396     for source, wf in conn.execute(""" -- some SQL hackery here to parse "source"
0397         SELECT DISTINCT source, substr(source, instr(source, "wf ")+3)*1 FROM relation ORDER BY 2, 1;"""):
0398       wfs[wf].append('<a href="/workflow/%s">%s</a>' % (urllib.quote(source), formatsource(source)))
0399     out.append("<ul>")
0400     for wf in wfs:
0401       out.append('<li>' + " ".join(wfs[wf]) + "</li>")
0402     out.append("</ul>")
0403 
0404     out.append("<ul>")
0405     for f in conn.execute("SELECT name FROM file ORDER BY name;"):
0406       name = escape(f[0])
0407       out.append('<li><a href="/%s">%s</a></li>' % (name, name))
0408     out.append("</ul>")
0409 
0410     return "\n".join(out)
0411 
0412   def showworkflow(source):
0413     source = urllib.unquote(source)
0414     cur = conn.execute("""
0415       SELECT DISTINCT file.name FROM relation 
0416         INNER JOIN trace ON place = trace.id
0417         INNER JOIN file ON file.id = trace.file
0418       WHERE relation.source = ?
0419       ORDER BY file.name;
0420     """, (source, ))
0421     out = ["Files used by workflow %s: <ul>" % formatsource(source)]
0422     for f in cur:
0423       name = escape(f[0])
0424       out.append('<li><a href="/%s">%s</a></li>' % (name, name))
0425     out.append("</ul>")
0426     return "\n".join(out)
0427 
0428   def showfile(filename):
0429     out = []
0430     out.append('<script src="https://rawgit.com/google/code-prettify/master/src/prettify.js"></script>')
0431     out.append('<link rel="stylesheet" href="https://rawgit.com/google/code-prettify/master/src/prettify.css"></link>')
0432 
0433     lines = None
0434     for d in STRIPPATHS:
0435       try:
0436         with open(d + filename) as f:
0437           lines = f.readlines()
0438           out.append("Read %s" % f.name)
0439         break
0440       except:
0441         pass
0442 
0443     if lines:
0444       cur = conn.execute("""
0445       SELECT DISTINCT trace.line, source FROM relation 
0446         INNER JOIN trace on relation.place = trace.id 
0447         INNER JOIN file ON trace.file == file.id 
0448         WHERE file.name == ? ORDER BY line, source;""", (filename,))
0449       sourceinfo = defaultdict(list)
0450       for line, source in cur:
0451         sourceinfo[line].append(source)
0452 
0453       out.append('<pre class="prettyprint linenums">')
0454       for i, l in enumerate(lines):
0455         # put the text into data-tag here and move it later, to not mess up syntax HL
0456         tags = [formatsource(source, '<em class="%%s" data-tag="%%s" data-line="%d"></em>' % (i+1)) for source in sourceinfo[i+1]]
0457         out.append(escape(l).rstrip() + "".join(tags))
0458       out.append('</pre>')
0459 
0460       out.append("""<script type="text/javascript">
0461       PR.prettyPrint();
0462       clickfunc = function(evt) {
0463         document.querySelectorAll("li > iframe, li > br").forEach(function(e) {e.remove()}); 
0464         dest = "/info" + window.location.pathname + ":" + this.getAttribute("data-line");
0465         this.parentElement.insertAdjacentHTML("beforeend", '<br><iframe width="90%" height="500px" frameborder="0" src="' + dest + '"></iframe><br>');
0466       };
0467       document.querySelectorAll("li > em").forEach(function(e) {
0468         e.innerText = e.getAttribute("data-tag");
0469         e.onclick = clickfunc;
0470       })
0471 
0472       n = 1*window.location.hash.replace("#", "");
0473       if (n > 0) {
0474         li = document.querySelectorAll("li")[n-1];
0475         li.style = "background: #ee7";
0476         li.scrollIntoView();
0477       }
0478       </script>""")
0479 
0480     else:
0481       out.append("Could not find %s" % filename)
0482 
0483     return "\n".join(out)
0484 
0485       
0486   def showinfo(filename, line):
0487     line = int(line)
0488     out = []
0489     def queryandoutput(startfrom, to, directiontext):
0490       # we format in the side of the relation to query for here...
0491       cur = conn.execute("""
0492         SELECT place_type, -- why did we trace this line?
0493             <to>file.name, <to>trace.line, -- where was it used?
0494             usedby, usedby_type, relation, -- what was it used for?
0495             place, source -- why did this code run?
0496           FROM relation 
0497           INNER JOIN trace AS placetrace  ON placetrace.id  = relation.place
0498           INNER JOIN trace AS usedbytrace ON usedbytrace.id = relation.usedby
0499           INNER JOIN file AS placefile  ON placefile.id  = placetrace.file
0500           INNER JOIN file AS usedbyfile ON usedbyfile.id = usedbytrace.file
0501         WHERE <from>file.name = ? AND <from>trace.line = ?
0502         LIMIT 1000; """
0503         .replace("<from>", startfrom).replace("<to>", to), (filename, line))
0504       out.append("<p>%s %s <ul>" % (formatplace(filename, line), directiontext))
0505       for place_type, pname, pline, usedby, usedby_type, relation, place, source in cur:
0506         out.append(
0507           '<li><tt>%s</tt> at %s by <tt>%s</tt> as <a href="/why/%d">%s</a> <a href="/why/%d">in</a> %s</li>'
0508           % (escape(place_type), formatplace(pname, pline), escape(usedby_type), usedby, escape(relation), place, formatsource(source)))
0509       out.append("</ul></p>")
0510 
0511     queryandoutput("place", "usedby", "is used as")
0512     queryandoutput("usedby", "place", "uses")
0513 
0514     return "\n".join(out)
0515 
0516   def showwhy(id):
0517     id = int(id)
0518     # this (WHERE before recursion) will optimize better than the view.
0519     cur = conn.execute("""
0520       WITH RECURSIVE fulltrace(level, baseid, parent, file, name, line) AS (
0521         SELECT 1 AS level, trace.id, parent, trace.file, file.name, line FROM trace 
0522           INNER JOIN file ON file.id = trace.file
0523         WHERE trace.id = ?
0524         UNION SELECT level+1, baseid, trace.parent, trace.file, file.name, trace.line FROM fulltrace 
0525           INNER JOIN trace ON trace.id = fulltrace.parent
0526           INNER JOIN file ON file.id = trace.file)
0527       SELECT name, line FROM fulltrace ORDER BY level;""", (id,))
0528     out = []
0529     out.append("Full stack trace:<ul>")
0530     for name, line in cur:
0531       out.append('<li>%s</li>' % formatplace(name, line))
0532     out.append("</ul>")
0533     return "\n".join(out)
0534 
0535   ROUTES = [
0536     (re.compile('/workflow/(.*)$'), showworkflow),
0537     (re.compile('/info/(.*):(\d+)$'), showinfo),
0538     (re.compile('/why/(\d+)$'), showwhy),
0539     (re.compile('/([^.]*[.]?[^.]+[.]?[^.]*)$'), showfile),
0540     (re.compile('/$'), index),
0541   ]
0542 
0543   def do_GET(self):
0544     try:
0545       res = None
0546       for pattern, func in ROUTES:
0547         m = pattern.match(self.path)
0548         if m:
0549           res = func(*m.groups())
0550           break
0551 
0552       if res:
0553         self.send_response(200, "Here you go")
0554         self.send_header("Content-Type", "text/html; charset=utf-8")
0555         self.end_headers()
0556         self.wfile.write("""<html><style>
0557           body {
0558             font-family: sans;
0559           }
0560           em {
0561             cursor: pointer;
0562             padding: 0 2px;
0563             margin: 1 2px;
0564             background: #999;
0565           }
0566           em.ALCA {background: #ee9; }
0567           em.RECO {background: #9e9; }
0568           em.HARVEST {background: #99e; }
0569           em.online {background: #e99; }
0570         </style><body>""")
0571         self.wfile.write(res)
0572         self.wfile.write("</body></html>")
0573         self.wfile.close()
0574       else:
0575         self.send_response(400, "Something went wrong")
0576     except:
0577       trace = traceback.format_exc()
0578       self.send_response(500, "Things went very wrong")
0579       self.send_header("Content-Type", "text/plain; charset=utf-8")
0580       self.end_headers()
0581       self.wfile.write(trace)
0582       self.wfile.close()
0583 
0584   Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
0585   Handler.do_GET = do_GET
0586   httpd = SocketServer.TCPServer(("",SERVE_PORT), Handler)
0587   print("serving at port", SERVE_PORT)
0588   httpd.serve_forever()
0589 
0590 if __name__ == '__main__':
0591   if sys.argv[1] == "serve":
0592     serve_main()
0593   else:
0594     main()
0595