Skip to content

Commit c600731

Browse files
authored
Add Prometheus metrics for HTTPX requests (#130)
Add a new prometheus metrics module and hook it into HTTPX event handlers. Created src/om1_utils/prometheus/__init__.py which defines Histograms and Gauges for request duration, upstream total, upstream TTFB, and proxy total (including `_last_seconds` gauges). Updated src/om1_utils/httpx/__init__.py to import these metrics, handle missing request start_time (log a warning and skip metrics), compute elapsed time, and record observe/set metrics by parsing relevant response headers (convert ms to seconds and ignore invalid values).
1 parent 04c4dce commit c600731

2 files changed

Lines changed: 117 additions & 1 deletion

File tree

src/om1_utils/httpx/__init__.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33

44
import httpx
55

6+
from ..prometheus import (
7+
om1_http_proxy_total_last_seconds,
8+
om1_http_proxy_total_seconds,
9+
om1_http_request_duration_last_seconds,
10+
om1_http_request_duration_seconds,
11+
om1_http_upstream_total_last_seconds,
12+
om1_http_upstream_total_seconds,
13+
om1_http_upstream_ttfb_last_seconds,
14+
om1_http_upstream_ttfb_seconds,
15+
)
16+
617

718
def get_httpx_event_hooks() -> dict[str, list]:
819
"""
@@ -34,7 +45,14 @@ def log_response(response: httpx.Response):
3445
response : httpx.Response
3546
The HTTP response object to log.
3647
"""
37-
start_time = response.request.extensions.get("start_time", 0)
48+
start_time = response.request.extensions.get("start_time", None)
49+
if start_time is None:
50+
logging.warning(
51+
f"HTTP {response.request.method} {response.request.url} - "
52+
"No start_time recorded, skipping metrics"
53+
)
54+
return
55+
3856
elapsed = (time.perf_counter() - start_time) * 1000
3957
http_version = response.http_version
4058
proxy_parse_total_time = response.headers.get("x-proxy-parse-ms", "?")
@@ -52,6 +70,55 @@ def log_response(response: httpx.Response):
5270
f"Proxy Total Time: {proxy_total_time} ms"
5371
)
5472

73+
method = response.request.method
74+
status_code = str(response.status_code)
75+
host = str(response.request.url.host)
76+
path = str(response.request.url.path)
77+
elapsed_s = elapsed / 1000.0
78+
79+
om1_http_request_duration_seconds.labels(
80+
host=host, path=path, method=method, status_code=status_code
81+
).observe(elapsed_s)
82+
om1_http_request_duration_last_seconds.labels(
83+
host=host, path=path, method=method, status_code=status_code
84+
).set(elapsed_s)
85+
86+
if upstream_total_time != "?":
87+
try:
88+
val = float(upstream_total_time) / 1000.0
89+
om1_http_upstream_total_seconds.labels(
90+
host=host, path=path, method=method, status_code=status_code
91+
).observe(val)
92+
om1_http_upstream_total_last_seconds.labels(
93+
host=host, path=path, method=method, status_code=status_code
94+
).set(val)
95+
except ValueError:
96+
pass
97+
98+
if upstream_ttfb_time != "?":
99+
try:
100+
val = float(upstream_ttfb_time) / 1000.0
101+
om1_http_upstream_ttfb_seconds.labels(
102+
host=host, path=path, method=method, status_code=status_code
103+
).observe(val)
104+
om1_http_upstream_ttfb_last_seconds.labels(
105+
host=host, path=path, method=method, status_code=status_code
106+
).set(val)
107+
except ValueError:
108+
pass
109+
110+
if proxy_total_time != "?":
111+
try:
112+
val = float(proxy_total_time) / 1000.0
113+
om1_http_proxy_total_seconds.labels(
114+
host=host, path=path, method=method, status_code=status_code
115+
).observe(val)
116+
om1_http_proxy_total_last_seconds.labels(
117+
host=host, path=path, method=method, status_code=status_code
118+
).set(val)
119+
except ValueError:
120+
pass
121+
55122
return {
56123
"request": [log_request],
57124
"response": [log_response],
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from prometheus_client import Gauge, Histogram
2+
3+
om1_http_request_duration_seconds = Histogram(
4+
"om1_http_request_duration_seconds",
5+
"Total HTTP request duration (client-side) in seconds",
6+
["host", "path", "method", "status_code"],
7+
)
8+
9+
om1_http_upstream_total_seconds = Histogram(
10+
"om1_http_upstream_total_seconds",
11+
"Upstream total time in seconds (from x-upstream-total-ms header)",
12+
["host", "path", "method", "status_code"],
13+
)
14+
15+
om1_http_upstream_ttfb_seconds = Histogram(
16+
"om1_http_upstream_ttfb_seconds",
17+
"Upstream TTFB in seconds (from x-upstream-ttfb-ms header)",
18+
["host", "path", "method", "status_code"],
19+
)
20+
21+
om1_http_proxy_total_seconds = Histogram(
22+
"om1_http_proxy_total_seconds",
23+
"Proxy total time in seconds (from x-proxy-total-ms header)",
24+
["host", "path", "method", "status_code"],
25+
)
26+
27+
om1_http_request_duration_last_seconds = Gauge(
28+
"om1_http_request_duration_last_seconds",
29+
"Most recent HTTP request duration (client-side) in seconds",
30+
["host", "path", "method", "status_code"],
31+
)
32+
33+
om1_http_upstream_total_last_seconds = Gauge(
34+
"om1_http_upstream_total_last_seconds",
35+
"Most recent upstream total time in seconds",
36+
["host", "path", "method", "status_code"],
37+
)
38+
39+
om1_http_upstream_ttfb_last_seconds = Gauge(
40+
"om1_http_upstream_ttfb_last_seconds",
41+
"Most recent upstream TTFB in seconds",
42+
["host", "path", "method", "status_code"],
43+
)
44+
45+
om1_http_proxy_total_last_seconds = Gauge(
46+
"om1_http_proxy_total_last_seconds",
47+
"Most recent proxy total time in seconds",
48+
["host", "path", "method", "status_code"],
49+
)

0 commit comments

Comments
 (0)