Getting started¶
This page takes you from installing the package to reading your first report. It takes about five minutes.
Install¶
executiontimer needs Python 3.11 or newer and has no dependencies.
pip install executiontimer
uv add executiontimer
The install name is executiontimer, but the module you import is execution_timer:
from execution_timer import TimerContext
Time a section¶
Wrap the code you want to measure in a with TimerContext(...) block and give it a name:
import time
from execution_timer import TimerContext, get_execution_times_report
with TimerContext("load_data"):
time.sleep(0.1)
print(get_execution_times_report())
Total time: 104.727 ms.
load_data: 104.727 ms (100.00%)
Timings are recorded in one registry for the whole process. Every section you time, in any module, ends up in the same report.
Nest sections¶
A section started inside another one is recorded beneath it. You don’t pass parents around: the nesting of your code is the nesting of the report.
from execution_timer import clear_execution_timings
clear_execution_timings()
with TimerContext("pipeline"):
with TimerContext("load"):
time.sleep(0.02)
with TimerContext("transform"):
time.sleep(0.05)
with TimerContext("validate"):
time.sleep(0.01)
print(get_execution_times_report())
Total time: 89.826 ms.
pipeline: 89.826 ms (100.00%)
.. load: 22.073 ms (24.57%)
.. transform: 67.726 ms (75.40%)
.. .. validate: 12.549 ms (13.97%)
How to read the report:
Indentation shows nesting.
validateran insidetransform, which ran insidepipeline.Total time is the sum of the top-level sections. Nested sections are part of their parent’s time, so they aren’t added again.
Percentages are shares of the total, at every level.
validatetook 13.97% of the whole run, not 13.97% oftransform.Times have three decimals, in
µs,msors, whichever keeps the number below 1000, so even very fast sections show a real value.
Calling clear_execution_timings() starts over with an empty
registry.
Use it as a decorator¶
The same object works as a decorator. Every call to the function is timed and added to the same section:
@TimerContext("parse")
def parse(line: str) -> list[str]:
return line.split(",")
for line in ["a,b", "c,d", "e,f"]:
parse(line)
Decorated calls nest like with blocks: calling parse inside pipeline records it as
pipeline › parse. async def functions work too, and the timing covers the whole
await. See Timing code.
Group sections with categories¶
Tag sections with a category to total them across the tree:
from execution_timer import get_total_category_time
with TimerContext("download", category="io"):
time.sleep(0.02)
with TimerContext("save", category="io"):
time.sleep(0.01)
print(f"{get_total_category_time('io'):.2f} s spent on I/O")
0.03 s spent on I/O
Categories are plain strings, so use whatever fits your code. See Categories.
Get the numbers out¶
Besides the text report, you can get the timings as data:
from execution_timer import get_execution_timings, get_total_time, log_execution_times
get_total_time() # seconds across all top-level sections
get_execution_timings() # {("pipeline", "load"): {"time": 0.02, "category": "default"}, ...}
log_execution_times() # the report, logged at INFO level
Or as JSON for a dashboard, a CI artifact or a language model:
from execution_timer import save_execution_timings_json
save_execution_timings_json("timings.json")
See Reports and export for the formats.
Next steps¶
Timing code: decorators, async functions, exceptions and recursion.
Counters and flattening: timing each iteration of a loop.
Threads, asyncio and generators: threads, asyncio tasks and generators.
API reference: every function and type.