import time
__all__ = ["Profiler", "NullProfiler"]
[docs]class NullProfiler:
"""This is a null profiler that does nothing"""
[docs] def __init__(self, name: str = None, parent=None):
pass
def is_null(self) -> bool:
return True
[docs] def __str__(self):
return "No profiling data collected"
def start(self, name: str = None):
return self
def stop(self):
return self
[docs]class Profiler:
"""This is a simple profiling class that supports manual
instrumenting of the code. It is used for sub-function
profiling.
"""
[docs] def __init__(self, name: str = None, parent=None):
self._name = name
self._parent = parent
self._children = []
self._start = None
self._end = None
[docs] def is_null(self) -> bool:
"""Return whether this is a null profiler"""
return False
def _to_string(self):
"""Used to write the results of profiling as a report"""
lines = []
t = self.total()
if self._name is None:
if self._parent is None:
self._name = "Total time"
if t:
if len(self._children) > 0:
ct = self.child_total()
if ct:
lines.append("%s: %.3f ms (%.3f ms)" % (self._name, t, ct))
else:
lines.append("%s: %.3f ms (???) ms" % (self._name, t))
else:
lines.append("%s: %.3f ms" % (self._name, t))
elif self._start is None:
lines.append(f"{self._name}")
else:
lines.append(f"{self._name}: still timing...")
for child in self._children:
clines = child._to_string()
lines.append(f" \\-{clines[0]}")
if len(clines) > 1:
for l in clines[1:]:
lines.append(f" {l}")
return lines
[docs] def __str__(self):
"""Return the results of profiling as a report"""
return "\n".join(self._to_string())
[docs] def name(self) -> str:
"""Return the name of this profiler"""
return self._name
[docs] def child_total(self) -> float:
"""Return the sum of time spent in the child profilers"""
sum = None
for child in self._children:
t = child.total()
if t is not None:
if sum is None:
sum = t
else:
sum += t
return sum
[docs] def total(self) -> float:
"""Return the amount of time that was recorded for this
profiler in milliseconds (accurate to ~nanoseconds)
"""
if self._parent is None:
# this is the top-level profiler - just return the child total
return self.child_total()
elif self._end:
return (self._end - self._start) * 0.000001 # ns to ms
else:
return None
[docs] def start(self, name: str):
"""Start profiling the section called 'name'. This
returns the updated profiler, e.g.
p = Profiler()
p = p.start("long_loop")
# run some code
p = p.end()
print(p)
"""
p = Profiler(name=name, parent=self)
self._children.append(p)
p._start = time.time_ns()
return p
[docs] def stop(self):
"""Stop profiling. This records the end time
and returns the parent profiler (if we have one)
"""
end = time.time_ns()
if self._start is None:
from ._console import Console
Console.warning(f"You cannot stop profiler {self._name} as "
f"it has not been started!")
elif self._end is not None:
from ._console import Console
Console.warning(
f"WARNING: You cannot stop profiler {self._name} as "
f"it has already been stopped!")
else:
self._end = end
if self._parent:
return self._parent
else:
# no parent - just return this profiler
return self