Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2024-11-26 02:34:11

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