from __future__ import absolute_import, print_function, unicode_literals import json import time from zope.interface import implementer from ._interfaces import ITiming class Event: def __init__(self, name, when, **details): # data fields that will be dumped to JSON later self._name = name self._start = time.time() if when is None else float(when) self._stop = None self._details = details def detail(self, **details): self._details.update(details) def finish(self, when=None, **details): self._stop = time.time() if when is None else float(when) self.detail(**details) def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_tb): if exc_type: # inlineCallbacks uses a special exception (defer._DefGen_Return) # to deliver returnValue(), so if returnValue is used inside our # with: block, we'll mistakenly think it means something broke. # I've moved all returnValue() calls outside the 'with # timing.add()' blocks to avoid this, but if a new one # accidentally pops up, it'll get marked as an error. I used to # catch-and-release _DefGen_Return to avoid this, but removed it # because it requires referencing defer.py's private class self.finish(exception=str(exc_type)) else: self.finish() @implementer(ITiming) class DebugTiming: def __init__(self): self._events = [] def add(self, name, when=None, **details): ev = Event(name, when, **details) self._events.append(ev) return ev def write(self, fn, stderr): with open(fn, "wt") as f: data = [ dict( name=e._name, start=e._start, stop=e._stop, details=e._details, ) for e in self._events ] json.dump(data, f, indent=1) f.write("\n") print("Timing data written to %s" % fn, file=stderr)