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