Reports and export

All timings live in one registry for the whole process. You can read them as a text report, a log record, a dictionary or JSON. Every reader takes a snapshot, so you can call them while other threads are still recording.

The examples on this page use these timings:

import time

from execution_timer import TimerContext

with TimerContext("load_data", category="io"):
    time.sleep(0.02)

with TimerContext("solve"):
    for i in range(2):
        with TimerContext("step", category="gpu", counter=i):
            time.sleep(0.01)

Text report

get_execution_times_report() returns the report as a string, or "" if nothing has been recorded:

from execution_timer import get_execution_times_report

print(get_execution_times_report())
Total time: 48.159 ms.

load_data: 24.199 ms (50.25%)
solve: 23.960 ms (49.75%)
..  step: 23.904 ms (49.64%)

Sections appear depth-first, each followed by its children, in the order they were first entered. Percentages are shares of the total time at every level.

Times have three decimals, in µs, ms or s, whichever keeps the number below 1000. A 48 µs section reads 48.213 µs rather than rounding to zero. The dictionary and JSON readers below return plain seconds.

Logging

log_execution_times() logs the same report at INFO level, starting on its own line after the log record’s prefix:

import logging

from execution_timer import log_execution_times

logging.basicConfig(level=logging.INFO)
log_execution_times()
INFO:execution_timer._timer:
Total time: 48.159 ms.

load_data: 24.199 ms (50.25%)
solve: 23.960 ms (49.75%)
..  step: 23.904 ms (49.64%)

It logs to the execution_timer._timer logger unless you pass your own with logger=. If the logger has INFO disabled, it returns without building the report, so it costs almost nothing in production. If nothing has been recorded, it logs a warning instead.

As a dictionary

get_execution_timings() returns a new dictionary keyed by section path. Each value is a TimingReport with the seconds and the category:

from execution_timer import get_execution_timings

timings = get_execution_timings()

for path, report in timings.items():
    print(" › ".join(path), f"{report['time']:.3f} s", report["category"])
load_data 0.025 s io
solve 0.025 s default
solve › step 0.025 s gpu

Two more functions give totals directly:

from execution_timer import get_total_category_time, get_total_time

total = get_total_time()  # seconds across all top-level sections
gpu = get_total_category_time("gpu")  # seconds in the top-most "gpu" sections

JSON

get_execution_times_json() returns a JSON document and save_execution_timings_json() writes it to a file. Both take indent=, which is 2 by default; pass None for the most compact output.

from execution_timer import get_execution_times_json, save_execution_timings_json

document = get_execution_times_json()
path = save_execution_timings_json("timings.json")
{
  "total_time": 0.049851,
  "total_category_time": {
    "default": 0.024964,
    "gpu": 0.02491,
    "io": 0.024887
  },
  "sections": [
    {
      "name": "load_data",
      "path": [
        "load_data"
      ],
      "time": 0.024887,
      "category": "io"
    },
    {
      "name": "solve",
      "path": [
        "solve"
      ],
      "time": 0.024964,
      "category": "default"
    },
    {
      "name": "step",
      "path": [
        "solve",
        "step"
      ],
      "time": 0.02491,
      "category": "gpu"
    }
  ]
}

The document has three fields. Their types are exported as TimingsPayload and SectionRecord.

total_time

Seconds across all top-level sections, as from get_total_time().

total_category_time

Seconds per category, counted like get_total_category_time(), sorted by name.

sections

Every section in report order, with its own name, its full path, its time and its category.

Times are rounded to microseconds. The sections and the totals come from the same snapshot, so they always agree, and the category totals use the original, unmerged sections even when flatten=True merges counter variants with different categories.

The format suits dashboards and CI artifacts, and it’s easy for a language model to read if you want help interpreting a profile.

Flattening

Every function on this page except the two totals takes flatten=. The default, True, merges counter variants of a section; False keeps them apart. See Counters and flattening.