CMS 3D CMS Logo

cmsswConfigtrace.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 from __future__ import print_function
3 import os
4 import re
5 import sys
6 import inspect
7 import sqlite3
8 import tempfile
9 import traceback
10 import subprocess
11 from shutil import copy, rmtree
12 from collections import defaultdict
13 
14 
15 SERVE_PORT = 1234
16 
17 OUTFILE_TREE = "configtree.sqlite"
18 IGNORE_PACKAGES = ['FWCore/ParameterSet', 'DQMOffline/Configuration/scripts', "cmsRun"]
19 STRIPPATHS = [ # we will add the base dir from CMSSWCALLBASE env var here
20  os.environ["CMSSW_BASE"] + "/python/", os.environ["CMSSW_RELEASE_BASE"] + "/python/",
21  os.environ["CMSSW_BASE"] + "/cfipython/", os.environ["CMSSW_RELEASE_BASE"] + "/cfipython/"]
22 PREFIXINFO = [] # what we will show as "source" later
23 ARGV0 = "" # set in main
24 
25 
26 
27 # this already does a good job, but it is not enough
28 #import FWCore.GuiBrowsers.EnablePSetHistory
29 
30 # we need to patch out this to allow printing unlabled things
32 del FWCore.ParameterSet.Mixins._Labelable.__str__
33 
34 # then also trace Sequence construction so we can get a full tree
35 # (PSetHistory only tracks leaves)
37  # stolen from FWCore.GuiBrowsers.EnablePSetHistory, but needs more black-list
38  stack = inspect.stack()
39  i = 0
40  while i < len(stack) and len(stack[i])>=2 and any(map(lambda p: p in stack[i][1], IGNORE_PACKAGES)):
41  i += 1
42  res = stack[i: ]
43  j = 0
44  # for the other end we only use cmsRun (instead of IGNORE_PACKAGES) to avoid
45  # cutting traces in the middle that enter and leave IGNORE_PACKAGES
46  while j < len(res) and not 'cmsRun' in res[j][1]:
47  j += 1
48  res = res[:j]
49  if len(res)>=1 and len(res[0])>=3:
50  return res
51  else:
52  return [("unknown","unknown","unknown")]
53 
54 def trace_location(thing, name, extra = lambda thing, *args, **kwargs: thing):
55  old_method = getattr(thing, name)
56  def trace_location_hook(self, *args, **kwargs):
57  retval = old_method(self, *args, **kwargs)
58  where = auto_inspect()
59  #print("Called %s::%s at %s" % (thing.__name__, name, where[0][1:3]))
60  event = (name, tuple(w[1:3] for w in where), extra(self, *args, **kwargs))
61  if hasattr(self, "_trace_events"):
62  getattr(self, "_trace_events").append(event)
63  else:
64  # this bypasses setattr checks
65  self.__dict__["_trace_events"] = [ event ]
66 
67  return retval
68  setattr(thing, name, trace_location_hook)
69 
70 def flatten(*args):
71  # that was surprisingly hard...
72  return [x for x in args if not isinstance(x, list)] + sum(
73  [flatten(*x) for x in args if isinstance(x, list)], [])
74 
75 from FWCore.ParameterSet.SequenceTypes import _ModuleSequenceType, _SequenceCollection, Task, _UnarySequenceOperator, Schedule
76 from FWCore.ParameterSet.Modules import _Module, Source, ESSource, ESPrefer, ESProducer, Service, Looper
77 from FWCore.ParameterSet.Config import Process
78 # with this we can also track the '+' and '*' of modules, but it is slow
79 trace_location(_SequenceCollection, '__init__')
80 trace_location(_Module, '__init__')
81 trace_location(Source, '__init__')
82 trace_location(ESSource, '__init__')
83 trace_location(ESPrefer, '__init__')
84 trace_location(ESProducer, '__init__')
85 trace_location(Service, '__init__')
86 trace_location(Looper, '__init__')
87 trace_location(Process, '__init__')
88 trace_location(_UnarySequenceOperator, '__init__')
89 # lambda agrument names all match the original declarations, to make kwargs work
90 trace_location(_ModuleSequenceType, '__init__', lambda self, *arg: {'args': list(arg)})
91 trace_location(_ModuleSequenceType, 'copy')
92 trace_location(_ModuleSequenceType, 'associate', lambda self, *tasks: {'args': list(tasks)})
93 trace_location(_ModuleSequenceType, '__imul__', lambda self, rhs: {'rhs': rhs})
94 trace_location(_ModuleSequenceType, '__iadd__', lambda self, rhs: {'rhs': rhs})
95 trace_location(_ModuleSequenceType, 'copyAndExclude', lambda self, listOfModulesToExclude: {'olds': list(listOfModulesToExclude)})
96 trace_location(_ModuleSequenceType, 'replace', lambda self, original, replacement: {'old': original, 'new': replacement})
97 trace_location(_ModuleSequenceType, 'insert', lambda self, index, item: {'rhs': item})
98 trace_location(_ModuleSequenceType, 'remove', lambda self, something: {'old': something})
99 trace_location(Task, '__init__')
100 trace_location(Task, 'add', lambda self, *items: {'args': list(items)})
101 trace_location(Task, 'copy')
102 trace_location(Task, 'copyAndExclude', lambda self, listOfModulesToExclude: {'olds': list(listOfModulesToExclude)})
103 trace_location(Schedule, '__init__', lambda self, *args, **kwargs: {'args': flatten(list(args), kwargs.values())})
104 trace_location(Schedule, 'associate', lambda self, *tasks: {'args': list(tasks)})
105 trace_location(Schedule, 'copy')
106 # TODO: we could go deeper into Types and PSet, but that should not be needed for now.
107 
108 # lifted from EnablePSetHistory, we don't need all of that stuff.
109 def new_items_(self):
110  items = []
111  if self.source:
112  items += [("source", self.source)]
113  if self.looper:
114  items += [("looper", self.looper)]
115  #items += self.moduleItems_()
116  items += self.outputModules.items()
117  #items += self.sequences.items() # TODO: we don't need sequences that are not paths?
118  items += self.paths.items()
119  items += self.endpaths.items()
120  items += self.services.items()
121  items += self.es_producers.items()
122  items += self.es_sources.items()
123  items += self.es_prefers.items()
124  #items += self.psets.items()
125  #items += self.vpsets.items()
126  if self.schedule:
127  items += [("schedule", self.schedule)]
128  return tuple(items)
129 Process.items_=new_items_
130 
131 
132 
133 def collect_trace(thing, name, graph, parent):
134  # thing is what to look at, graph is the output list (of child, parent tuple pairs)
135  # thing could be pretty much anything.
136  classname = thing.__class__.__name__
137  if hasattr(thing, '_trace_events'):
138  events = list(getattr(thing, '_trace_events'))
139  getattr(thing, '_trace_events')[:] = [] # erase events so we can't end up in cycles
140  for action, loc, extra in events:
141  entry = (action, classname, loc)
142  graph.append((entry, parent, name))
143 
144  # items shall be a list of tuples (type, object) of the immediate children of the thing.
145  items = []
146  if hasattr(extra, 'items_'): # for cms.Process
147  items += extra.items_()
148  if hasattr(extra, '_seq'): # for sequences and similar
149  seq = getattr(extra, '_seq')
150  if seq:
151  items += [('seqitem', x) for x in getattr(seq, '_collection')]
152  if hasattr(extra, '_tasks'): # same
153  items += [('task', x) for x in getattr(extra, '_tasks')]
154  if hasattr(extra, '_collection'): # for cms.Task
155  items += [('subtask', x) for x in getattr(extra, '_collection')]
156  if hasattr(extra, '_operand'): # for _SeqenceNegation etc.
157  items += [('operand', getattr(extra, '_operand'))]
158  if isinstance(extra, dict): # stuff that we track explicitly^
159  for key, value in extra.items():
160  if isinstance(value, list):
161  items += [(key, x) for x in value]
162  else:
163  items += [(key, value)]
164 
165  for name, child in items:
166  collect_trace(child, name, graph, entry)
167 
168  else:
169  if not thing is None:
170  print("No _trace_events found in %s.\nMaybe turn on tracing for %s?" % (thing, classname))
171  print(" Found in %s" % (parent,))
172 
173 
174 def writeoutput(graph):
175  progname = " ".join(PREFIXINFO)
176  print("+Done running %s, writing output..." % progname)
177 
178  def formatfile(filename):
179  filename = os.path.abspath(filename)
180  for pfx in STRIPPATHS:
181  if filename.startswith(pfx):
182  filename = filename[len(pfx):]
183  return filename
184 
185  files = set()
186  for child, parent, relation in graph:
187  files.add(child[2][0][0])
188  files.add(parent[2][0][0])
189 
190  conn = sqlite3.connect(os.environ["CMSSWCALLTREE"])
191  cur = conn.cursor()
192  cur.executescript("""
193  CREATE TABLE IF NOT EXISTS file(id INTEGER PRIMARY KEY,
194  name TEXT, UNIQUE(name)
195  );
196  CREATE TABLE IF NOT EXISTS trace(id INTEGER PRIMARY KEY,
197  parent INTEGER, -- points into same table, recursively
198  file INTEGER, line INTEGER,
199  FOREIGN KEY(parent) REFERENCES trace(id),
200  FOREIGN KEY(file) REFERENCES file(id),
201  UNIQUE(parent, file, line)
202  );
203  CREATE TABLE IF NOT EXISTS relation(id INTEGER PRIMARY KEY,
204  place INTEGER,
205  place_type TEXT,
206  usedby INTEGER,
207  usedby_type TEXT,
208  relation TEXT,
209  source TEXT,
210  FOREIGN KEY(place) REFERENCES trace(id),
211  FOREIGN KEY(usedby) REFERENCES trace(id)
212  );
213  CREATE INDEX IF NOT EXISTS placeidx ON relation(place);
214  CREATE INDEX IF NOT EXISTS usedbyidx ON relation(usedby);
215  CREATE INDEX IF NOT EXISTS traceidx ON trace(file);
216  -- SQLite does not optimise that one well, but a VIEW is still nice to have...
217  CREATE VIEW IF NOT EXISTS fulltrace AS
218  WITH RECURSIVE fulltrace(level, baseid, parent, file, name, line) AS (
219  SELECT 1 AS level, trace.id, parent, trace.file, file.name, line FROM trace
220  INNER JOIN file ON file.id = trace.file
221  UNION SELECT level+1, baseid, trace.parent, trace.file, file.name, trace.line FROM fulltrace
222  INNER JOIN trace ON trace.id = fulltrace.parent
223  INNER JOIN file ON file.id = trace.file)
224  SELECT * FROM fulltrace;
225  """)
226  cur.executemany("INSERT OR IGNORE INTO file(name) VALUES (?);",
227  ((formatfile(f),) for f in files))
228  def inserttrace(loc):
229  parent = 0
230  for filename, line in reversed(loc):
231  conn.execute("INSERT OR IGNORE INTO trace(parent, file, line) SELECT ?, id, ? FROM file WHERE name == ?;", (parent, line, formatfile(filename)))
232  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))
233  for row in cur:
234  parent = row[0]
235  return parent
236 
237  for child, parent, relation in graph:
238  cevt, cclassname, cloc = child
239  pevt, pclassname, ploc = parent
240  place = inserttrace(cloc)
241  usedby = inserttrace(ploc)
242  cur.execute("INSERT OR IGNORE INTO relation(place, place_type, usedby, usedby_type, relation, source) VALUES (?,?,?,?,?,?);", (
243  place, "%s::%s" % (cclassname, cevt),
244  usedby, "%s::%s" % (pclassname, pevt),
245  relation, progname
246  ))
247  conn.commit()
248  conn.close()
249 
250 
251 
252 def addprefixinfo(argv):
253  cwd = os.path.abspath(os.getcwd())
254  wf = re.match(".*/(\d+\.\d+)_", cwd)
255  if wf:
256  PREFIXINFO.append("wf")
257  PREFIXINFO.append(wf.groups()[0])
258  online = re.match("(.*/)?(.*)_dqm_sourceclient-live_cfg\.py", argv[0])
259  if online:
260  PREFIXINFO.append("online")
261  PREFIXINFO.append(online.groups()[1])
262  step = re.match("(step\d+)_.*\.py", argv[0])
263  if step:
264  PREFIXINFO.append(step.groups()[0])
265  processing = re.match("step\d+_.*(RECO|ALCA|HARVEST).*\.py", argv[0])
266  if processing:
267  PREFIXINFO.append(processing.groups()[0])
268  if not PREFIXINFO:
269  PREFIXINFO.append(argv[0])
270 
271 def setupenv():
272  bindir = tempfile.mkdtemp()
273  print("+Setting up in ", bindir)
274  os.symlink(ARGV0, bindir + "/cmsRun")
275  os.environ["PATH"] = bindir + ":" + os.environ["PATH"]
276  os.environ["CMSSWCALLTREE"] = bindir + "/" + OUTFILE_TREE
277  os.environ["CMSSWCALLBASE"] = os.path.abspath(os.getcwd()) + "/"
278  with open(os.environ["CMSSWCALLTREE"], "w") as f:
279  pass
280  return bindir
281 
282 def cleanupenv(tmpdir):
283  print("+Cleaning up ", tmpdir)
284  copy(os.environ["CMSSWCALLTREE"], ".")
285  rmtree(tmpdir)
286 
287 
288 def trace_command(argv):
289  tmpdir = None
290  if not "CMSSWCALLTREE" in os.environ:
291  tmpdir = setupenv()
292 
293  subprocess.call(argv)
294 
295  if tmpdir:
296  cleanupenv(tmpdir)
297 
298 def trace_python(prog_argv, path):
299  graph = []
300  sys.argv = prog_argv
301  progname = prog_argv[0]
302  file_path = searchinpath(progname, path)
303  try:
304  with open(file_path) as fp:
305  code = compile(fp.read(), progname, 'exec')
306  globals = {}
307  try:
308  exec code in globals, globals
309  except:
310  print(traceback.format_exc())
311  finally:
312  # reporting is only possible if the config was executed successfully.
313  # we still do it in case of an exception, which can happen after convertToUnscheduled()
314  print("+Collecting trace information from %s..." % globals["process"])
315  collect_trace(globals["process"], 'cmsrun', graph, ('cmsRun', '', ((progname, 0),)))
316  writeoutput(graph)
317 
318  except OSError as err:
319  print("+Cannot run file %r because: %s" % (sys.argv[0], err))
320  sys.exit(1)
321  except SystemExit:
322  pass
323  # this is not necessarily reached at all.
324  sys.exit(0)
325 
326 def searchinpath(progname, path):
327  # Search $PATH. There seems to be no pre-made function for this.
328  for entry in path:
329  file_path = os.path.join(entry, progname)
330  if os.path.isfile(file_path):
331  break
332  if not os.path.isfile(file_path):
333  print("+Cannot find program (%s) in modified $PATH (%s)." % (progname, path))
334  sys.exit(1)
335  print("+Found %s as %s in %s." % (progname, file_path, path))
336  return file_path
337 
338 
339 def help():
340  print("Usage: %s <some cmssw commandline>" % (sys.argv[0]))
341  print(" The given programs will be executed, instrumenting calls to cmsRun.")
342  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)
343  print(" The callgraph output lists edges pointing from each function to the one calling it.")
344  print(" To view the results using a simple webpage, use\n %s serve" % sys.argv[0])
345  print("Examples:")
346  print(" %s runTheMatrix.py -l 1000 --ibeos" % sys.argv[0])
347 
348 def main():
349  print("+Running cmsswConfigtrace...")
350  global ARGV0
351  ARGV0 = sys.argv[0]
352  if sys.argv[0].endswith('cmsRun'):
353  print("+Wrapping cmsRun...")
354  addprefixinfo(sys.argv[1:])
355  STRIPPATHS.append(os.environ["CMSSWCALLBASE"])
356  trace_python(sys.argv[1:], ["."])
357  return
358  if len(sys.argv) <= 1:
359  help()
360  return
361  # else
362  print("+Running command with tracing %s..." % sys.argv[1:])
363  trace_command(sys.argv[1:])
364 
365 
366 
368  # we use STRIPPATHS as a search path to find code files.
369  STRIPPATHS.append(os.path.abspath(os.getcwd()) + "/")
370 
371  import SimpleHTTPServer
372  import SocketServer
373  import urllib
374 
375  conn = sqlite3.connect(OUTFILE_TREE)
376 
377  def escape(s):
378  return str(s).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
379 
380  def formatsource(source, formatstr = '<em class="%s">%s</em>'):
381  processings = ["ALCA", "RECO", "HARVEST", "online"]
382  info = source.split(" ")
383  processing = " ".join(filter(lambda x: x in processings, info))
384  source = " ".join(filter(lambda x: x not in processings, info))
385  return formatstr % (processing, escape(source))
386 
387  def formatplace(filename, line):
388  MAXLEN = 80
389  shortname = filename[ :(MAXLEN-3)/2] + "..." + filename[-(MAXLEN-3)/2: ] if len(filename) > MAXLEN else filename
390  return '<a href="/%s#%s" target="_blank">%s:%s</a>' % (escape(filename), line, escape(shortname), line)
391 
392  def index():
393  out = [escape('goto to /<filename> for info about a file')]
394 
395  wfs = defaultdict(list)
396  for source, wf in conn.execute(""" -- some SQL hackery here to parse "source"
397  SELECT DISTINCT source, substr(source, instr(source, "wf ")+3)*1 FROM relation ORDER BY 2, 1;"""):
398  wfs[wf].append('<a href="/workflow/%s">%s</a>' % (urllib.quote(source), formatsource(source)))
399  out.append("<ul>")
400  for wf in wfs:
401  out.append('<li>' + " ".join(wfs[wf]) + "</li>")
402  out.append("</ul>")
403 
404  out.append("<ul>")
405  for f in conn.execute("SELECT name FROM file ORDER BY name;"):
406  name = escape(f[0])
407  out.append('<li><a href="/%s">%s</a></li>' % (name, name))
408  out.append("</ul>")
409 
410  return "\n".join(out)
411 
412  def showworkflow(source):
413  source = urllib.unquote(source)
414  cur = conn.execute("""
415  SELECT DISTINCT file.name FROM relation
416  INNER JOIN trace ON place = trace.id
417  INNER JOIN file ON file.id = trace.file
418  WHERE relation.source = ?
419  ORDER BY file.name;
420  """, (source, ))
421  out = ["Files used by workflow %s: <ul>" % formatsource(source)]
422  for f in cur:
423  name = escape(f[0])
424  out.append('<li><a href="/%s">%s</a></li>' % (name, name))
425  out.append("</ul>")
426  return "\n".join(out)
427 
428  def showfile(filename):
429  out = []
430  out.append('<script src="https://rawgit.com/google/code-prettify/master/src/prettify.js"></script>')
431  out.append('<link rel="stylesheet" href="https://rawgit.com/google/code-prettify/master/src/prettify.css"></link>')
432 
433  lines = None
434  for d in STRIPPATHS:
435  try:
436  with open(d + filename) as f:
437  lines = f.readlines()
438  out.append("Read %s" % f.name)
439  break
440  except:
441  pass
442 
443  if lines:
444  cur = conn.execute("""
445  SELECT DISTINCT trace.line, source FROM relation
446  INNER JOIN trace on relation.place = trace.id
447  INNER JOIN file ON trace.file == file.id
448  WHERE file.name == ? ORDER BY line, source;""", (filename,))
449  sourceinfo = defaultdict(list)
450  for line, source in cur:
451  sourceinfo[line].append(source)
452 
453  out.append('<pre class="prettyprint linenums">')
454  for i, l in enumerate(lines):
455  # put the text into data-tag here and move it later, to not mess up syntax HL
456  tags = [formatsource(source, '<em class="%%s" data-tag="%%s" data-line="%d"></em>' % (i+1)) for source in sourceinfo[i+1]]
457  out.append(escape(l).rstrip() + "".join(tags))
458  out.append('</pre>')
459 
460  out.append("""<script type="text/javascript">
461  PR.prettyPrint();
462  clickfunc = function(evt) {
463  document.querySelectorAll("li > iframe, li > br").forEach(function(e) {e.remove()});
464  dest = "/info" + window.location.pathname + ":" + this.getAttribute("data-line");
465  this.parentElement.insertAdjacentHTML("beforeend", '<br><iframe width="90%" height="500px" frameborder="0" src="' + dest + '"></iframe><br>');
466  };
467  document.querySelectorAll("li > em").forEach(function(e) {
468  e.innerText = e.getAttribute("data-tag");
469  e.onclick = clickfunc;
470  })
471 
472  n = 1*window.location.hash.replace("#", "");
473  if (n > 0) {
474  li = document.querySelectorAll("li")[n-1];
475  li.style = "background: #ee7";
476  li.scrollIntoView();
477  }
478  </script>""")
479 
480  else:
481  out.append("Could not find %s" % filename)
482 
483  return "\n".join(out)
484 
485 
486  def showinfo(filename, line):
487  line = int(line)
488  out = []
489  def queryandoutput(startfrom, to, directiontext):
490  # we format in the side of the relation to query for here...
491  cur = conn.execute("""
492  SELECT place_type, -- why did we trace this line?
493  <to>file.name, <to>trace.line, -- where was it used?
494  usedby, usedby_type, relation, -- what was it used for?
495  place, source -- why did this code run?
496  FROM relation
497  INNER JOIN trace AS placetrace ON placetrace.id = relation.place
498  INNER JOIN trace AS usedbytrace ON usedbytrace.id = relation.usedby
499  INNER JOIN file AS placefile ON placefile.id = placetrace.file
500  INNER JOIN file AS usedbyfile ON usedbyfile.id = usedbytrace.file
501  WHERE <from>file.name = ? AND <from>trace.line = ?
502  LIMIT 1000; """
503  .replace("<from>", startfrom).replace("<to>", to), (filename, line))
504  out.append("<p>%s %s <ul>" % (formatplace(filename, line), directiontext))
505  for place_type, pname, pline, usedby, usedby_type, relation, place, source in cur:
506  out.append(
507  '<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>'
508  % (escape(place_type), formatplace(pname, pline), escape(usedby_type), usedby, escape(relation), place, formatsource(source)))
509  out.append("</ul></p>")
510 
511  queryandoutput("place", "usedby", "is used as")
512  queryandoutput("usedby", "place", "uses")
513 
514  return "\n".join(out)
515 
516  def showwhy(id):
517  id = int(id)
518  # this (WHERE before recursion) will optimize better than the view.
519  cur = conn.execute("""
520  WITH RECURSIVE fulltrace(level, baseid, parent, file, name, line) AS (
521  SELECT 1 AS level, trace.id, parent, trace.file, file.name, line FROM trace
522  INNER JOIN file ON file.id = trace.file
523  WHERE trace.id = ?
524  UNION SELECT level+1, baseid, trace.parent, trace.file, file.name, trace.line FROM fulltrace
525  INNER JOIN trace ON trace.id = fulltrace.parent
526  INNER JOIN file ON file.id = trace.file)
527  SELECT name, line FROM fulltrace ORDER BY level;""", (id,))
528  out = []
529  out.append("Full stack trace:<ul>")
530  for name, line in cur:
531  out.append('<li>%s</li>' % formatplace(name, line))
532  out.append("</ul>")
533  return "\n".join(out)
534 
535  ROUTES = [
536  (re.compile('/workflow/(.*)$'), showworkflow),
537  (re.compile('/info/(.*):(\d+)$'), showinfo),
538  (re.compile('/why/(\d+)$'), showwhy),
539  (re.compile('/([^.]*[.]?[^.]+[.]?[^.]*)$'), showfile),
540  (re.compile('/$'), index),
541  ]
542 
543  def do_GET(self):
544  try:
545  res = None
546  for pattern, func in ROUTES:
547  m = pattern.match(self.path)
548  if m:
549  res = func(*m.groups())
550  break
551 
552  if res:
553  self.send_response(200, "Here you go")
554  self.send_header("Content-Type", "text/html; charset=utf-8")
555  self.end_headers()
556  self.wfile.write("""<html><style>
557  body {
558  font-family: sans;
559  }
560  em {
561  cursor: pointer;
562  padding: 0 2px;
563  margin: 1 2px;
564  background: #999;
565  }
566  em.ALCA {background: #ee9; }
567  em.RECO {background: #9e9; }
568  em.HARVEST {background: #99e; }
569  em.online {background: #e99; }
570  </style><body>""")
571  self.wfile.write(res)
572  self.wfile.write("</body></html>")
573  self.wfile.close()
574  else:
575  self.send_response(400, "Something went wrong")
576  except:
577  trace = traceback.format_exc()
578  self.send_response(500, "Things went very wrong")
579  self.send_header("Content-Type", "text/plain; charset=utf-8")
580  self.end_headers()
581  self.wfile.write(trace)
582  self.wfile.close()
583 
584  Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
585  Handler.do_GET = do_GET
586  httpd = SocketServer.TCPServer(("",SERVE_PORT), Handler)
587  print("serving at port", SERVE_PORT)
588  httpd.serve_forever()
589 
590 if __name__ == '__main__':
591  if sys.argv[1] == "serve":
592  serve_main()
593  else:
594  main()
595 
def collect_trace(thing, name, graph, parent)
bool any(const std::vector< T > &v, const T &what)
Definition: ECalSD.cc:37
def replace(string, replacements)
def writeoutput(graph)
def searchinpath(progname, path)
void print(TMatrixD &m, const char *label=nullptr, bool mathematicaFormat=false)
Definition: Utilities.cc:47
def cleanupenv(tmpdir)
def trace_python(prog_argv, path)
static std::string join(char **cmd)
Definition: RemoteFile.cc:19
def formatfile(filename)
Definition: main.py:1
#define str(s)