File indexing completed on 2023-03-17 10:57:38
0001
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
0015 SERVE_PORT = 1234
0016
0017 OUTFILE_TREE = "configtree.sqlite"
0018 IGNORE_PACKAGES = ['FWCore/ParameterSet', 'DQMOffline/Configuration/scripts', "cmsRun"]
0019 STRIPPATHS = [
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 = []
0023 ARGV0 = ""
0024
0025
0026
0027
0028
0029
0030
0031 import FWCore.ParameterSet.Mixins
0032 del FWCore.ParameterSet.Mixins._Labelable.__str__
0033
0034
0035
0036 def auto_inspect():
0037
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
0045
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
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
0065 self.__dict__["_trace_events"] = [ event ]
0066
0067 return retval
0068 setattr(thing, name, trace_location_hook)
0069
0070 def flatten(*args):
0071
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
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
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
0107
0108
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
0116 items += self.outputModules.items()
0117
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
0125
0126 if self.schedule:
0127 items += [("schedule", self.schedule)]
0128 return tuple(items)
0129 Process.items_=new_items_
0130
0131
0132
0133 def collect_trace(thing, name, graph, parent):
0134
0135
0136 classname = thing.__class__.__name__
0137 if hasattr(thing, '_trace_events'):
0138 events = list(getattr(thing, '_trace_events'))
0139 getattr(thing, '_trace_events')[:] = []
0140 for action, loc, extra in events:
0141 entry = (action, classname, loc)
0142 graph.append((entry, parent, name))
0143
0144
0145 items = []
0146 if hasattr(extra, 'items_'):
0147 items += extra.items_()
0148 if hasattr(extra, '_seq'):
0149 seq = getattr(extra, '_seq')
0150 if seq:
0151 items += [('seqitem', x) for x in getattr(seq, '_collection')]
0152 if hasattr(extra, '_tasks'):
0153 items += [('task', x) for x in getattr(extra, '_tasks')]
0154 if hasattr(extra, '_collection'):
0155 items += [('subtask', x) for x in getattr(extra, '_collection')]
0156 if hasattr(extra, '_operand'):
0157 items += [('operand', getattr(extra, '_operand'))]
0158 if isinstance(extra, dict):
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
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
0313
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
0324 sys.exit(0)
0325
0326 def searchinpath(progname, path):
0327
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
0362 print("+Running command with tracing %s..." % sys.argv[1:])
0363 trace_command(sys.argv[1:])
0364
0365
0366
0367 def serve_main():
0368
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("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
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
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
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
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