|
| 1 | +--- |
| 2 | +title: Share results |
| 3 | +--- |
| 4 | + |
1 | 5 | # Share results |
2 | 6 |
|
3 | | -**Problem:** You need to get measurements out of the process — to a log, a file, OpenTelemetry, or a metrics backend. |
| 7 | +**Problem:** You need to get measurements out of the process — to a log, a file, OpenTelemetry, Prometheus, or another metrics backend. |
4 | 8 |
|
5 | 9 | **Idea:** Use **`on_end`** (and optionally `on_start`) to push each measurement out when the run finishes. The callback receives the `Measurement` with `wall_time`, `cpu_time`, and `metadata` set. |
6 | 10 |
|
7 | 11 | ## Log |
8 | 12 |
|
9 | | -```python |
| 13 | +```python hl_lines="16" |
10 | 14 | import logging |
11 | 15 | from timerun import Timer |
12 | 16 |
|
@@ -61,23 +65,50 @@ from timerun import Timer |
61 | 65 | # tracer = get_tracer(__name__) |
62 | 66 |
|
63 | 67 | def on_start(m): |
64 | | - m.metadata["span"] = tracer.start_span("timerun") |
| 68 | + m.metadata["span"] = tracer.start_span("timerun") # (1)! |
65 | 69 |
|
66 | 70 | def on_end(m): |
67 | | - span = m.metadata.get("span") |
| 71 | + span = m.metadata.get("span") # (2)! |
68 | 72 | if span is None: |
69 | 73 | return # If on_start didn't set a span, skip. |
70 | 74 | span.set_attribute("wall_time_ns", m.wall_time.duration) |
71 | 75 | span.set_attribute("cpu_time_ns", m.cpu_time.duration) |
72 | 76 | for k, v in m.metadata.items(): |
73 | 77 | if k != "span" and v is not None: |
74 | 78 | span.set_attribute(k, str(v)) |
75 | | - span.end() |
| 79 | + span.end() # (3)! |
76 | 80 |
|
77 | 81 | with Timer(on_start=on_start, on_end=on_end): |
78 | 82 | do_work() |
79 | 83 | ``` |
80 | 84 |
|
81 | | -**Next:** [Analyze results](analyze-results.md) |
| 85 | +1. Start the span before the timed work runs so nested operations can attach to the same trace context if your tracer supports it. |
| 86 | +2. Retrieve the span object you stashed on the `Measurement`; guard in case `on_start` failed or was skipped. |
| 87 | +3. End the span after attributes are set so duration and metadata are recorded on the same span. |
| 88 | + |
| 89 | +## Prometheus |
| 90 | + |
| 91 | +Use the [Prometheus Python client](https://github.com/prometheus/client_python) (`pip install prometheus-client`). Register a histogram (or summary) and observe wall-clock seconds in `on_end`: |
| 92 | + |
| 93 | +```python |
| 94 | +from prometheus_client import Histogram |
| 95 | +from timerun import Timer |
| 96 | + |
| 97 | +OPERATION_SECONDS = Histogram( |
| 98 | + "timerun_operation_seconds", |
| 99 | + "Wall time for timed operations (seconds)", |
| 100 | + buckets=(0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, float("inf")), |
| 101 | +) |
| 102 | + |
| 103 | + |
| 104 | +def observe_wall_time(m): |
| 105 | + OPERATION_SECONDS.observe(m.wall_time.timedelta.total_seconds()) |
| 106 | + |
| 107 | + |
| 108 | +with Timer(on_end=observe_wall_time): |
| 109 | + do_work() |
| 110 | +``` |
| 111 | + |
| 112 | +Expose metrics from your process with `start_http_server` or your framework’s integration so Prometheus can scrape them. |
82 | 113 |
|
83 | | -For callback basics, see [Reference: Callbacks](../guide/callbacks.md). For the OpenTelemetry API, see the [OpenTelemetry Python docs](https://opentelemetry.io/docs/languages/python/). |
| 114 | +**See also:** [Guide: Callbacks](../guide/callbacks.md) for when callbacks run. For the OpenTelemetry API, see the [OpenTelemetry Python docs](https://opentelemetry.io/docs/languages/python/). |
0 commit comments