Source code for mantidimaging.core.utility.execution_timer
# Copyright (C) 2024 ISIS Rutherford Appleton Laboratory UKRI
# SPDX - License - Identifier: GPL-3.0-or-later
"""
Context managers for logging execution time or profile of code.
These are not used in the code, but are here as developer tools. They can be used by wrapping the code of interest::
with ExecutionProfiler(msg="a_slow_function()"):
a_slow_function()
They will display to the performance log if a logger object is not given. See the developer docs to enable performance
logging.
"""
from __future__ import annotations
import cProfile
import time
from io import StringIO
from logging import getLogger, Logger
from pstats import Stats
perf_logger = getLogger("perf." + __name__)
[docs]
class ExecutionTimer:
"""
Context manager used to time the execution of code in its context.
"""
def __init__(self, msg: str = 'Elapsed time', logger: Logger = perf_logger):
self.msg = msg
self.logger = logger
self.time_start: float | None = None
self.time_end: float | None = None
def __str__(self):
prefix = f'{self.msg}: ' if self.msg else ''
sec = self.total_seconds
return f'{prefix}{sec if sec else "unknown"} seconds'
def __enter__(self):
self.time_start = time.monotonic()
self.time_end = None
def __exit__(self, *args):
self.time_end = time.monotonic()
self.logger.info(str(self))
@property
def total_seconds(self):
"""
Gets the total number of seconds the timer was running for, returns
None if the timer has not been run or is still running.
"""
return self.time_end - self.time_start if \
self.time_start and self.time_end else None
[docs]
class ExecutionProfiler:
"""
Context manager used to profile the execution of code in its context.
"""
def __init__(self,
msg: str = 'Elapsed time',
logger: Logger = perf_logger,
max_lines: int = 20,
sort_by: str = "cumtime"):
self.msg = msg
self.logger = logger
self.max_lines = max_lines
self.sort_by = sort_by
self.pr = cProfile.Profile()
def __str__(self):
out = StringIO()
out.write(f'{self.msg}: \n' if self.msg else '')
ps = Stats(self.pr, stream=out).sort_stats(self.sort_by)
ps.print_stats()
return out.getvalue()
def __enter__(self):
self.pr.enable()
def __exit__(self, *args):
self.pr.disable()
if perf_logger.isEnabledFor(1):
for line in str(self).split("\n")[:self.max_lines]:
self.logger.info(line)