Files
bvandeusenandClaude Opus 5.5 a9998681f9
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m11s
CI / unit (push) Successful in 1m27s
CI / publish (push) Successful in 1m6s
test(core): the +Inf bucket is its le label, not its value (#5667)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:58:48 -04:00

118 lines
4.2 KiB
Python

"""The Prometheus text parser every scraper shares (steward/core/prometheus.py)."""
import math
from steward.core import prometheus
# Shaped like Fabled Archives' /metrics: HELP and TYPE lines, info and state
# gauges with several labels, counters, and an untyped stray.
ARCHIVES = """\
# HELP fabledarchives_build_info Build of the running server.
# TYPE fabledarchives_build_info gauge
fabledarchives_build_info{version="2026.10.11.1458",channel="dev",commit="c49b470"} 1
# TYPE fabledarchives_backup_target_state gauge
fabledarchives_backup_target_state{target="Steward",target_id="3",state="fresh"} 1
fabledarchives_backup_target_state{target="Steward",target_id="3",state="late"} 0
# TYPE fabledarchives_http_requests_total counter
fabledarchives_http_requests_total{route="GET /api/files",status="200"} 1027
fabledarchives_http_requests_total{route="GET /api/files",status="404"} 3
stray_metric 4.5
"""
def test_families_carry_their_declared_type():
fams = prometheus.parse(ARCHIVES)
assert fams["fabledarchives_build_info"].type == "gauge"
assert fams["fabledarchives_build_info"].help == "Build of the running server."
assert fams["fabledarchives_http_requests_total"].type == "counter"
assert fams["stray_metric"].type == "untyped"
assert list(fams) == [
"fabledarchives_build_info",
"fabledarchives_backup_target_state",
"fabledarchives_http_requests_total",
"stray_metric",
]
def test_labels_keep_their_order_and_values():
state = prometheus.parse(ARCHIVES)["fabledarchives_backup_target_state"]
assert [s.labels for s in state.samples] == [
(("target", "Steward"), ("target_id", "3"), ("state", "fresh")),
(("target", "Steward"), ("target_id", "3"), ("state", "late")),
]
assert [s.value for s in state.samples] == [1.0, 0.0]
reqs = prometheus.parse(ARCHIVES)["fabledarchives_http_requests_total"]
assert reqs.samples[0].label_dict() == {"route": "GET /api/files", "status": "200"}
def test_escaped_label_values_and_separators_inside_quotes():
text = 'm{a="say \\"hi\\"",b="back\\\\slash",c="two\\nlines",d="x,y}z"} 2\n'
(sample,) = prometheus.parse(text)["m"].samples
assert sample.label_dict() == {
"a": 'say "hi"', "b": "back\\slash", "c": "two\nlines", "d": "x,y}z",
}
def test_histogram_and_summary_samples_belong_to_their_family():
text = """\
# TYPE req_seconds histogram
req_seconds_bucket{le="0.1"} 3
req_seconds_bucket{le="+Inf"} 5
req_seconds_sum 0.9
req_seconds_count 5
# TYPE rpc summary
rpc{quantile="0.5"} 0.2
rpc_sum 7
rpc_count 30
"""
fams = prometheus.parse(text)
assert set(fams) == {"req_seconds", "rpc"}
assert [s.name for s in fams["req_seconds"].samples] == [
"req_seconds_bucket", "req_seconds_bucket", "req_seconds_sum", "req_seconds_count",
]
assert fams["req_seconds"].samples[1].label_dict() == {"le": "+Inf"}
assert [s.name for s in fams["rpc"].samples] == ["rpc", "rpc_sum", "rpc_count"]
def test_an_openmetrics_counter_owns_its_total_sample():
fams = prometheus.parse("# TYPE jobs counter\njobs_total 12\n")
assert list(fams) == ["jobs"]
assert fams["jobs"].type == "counter"
assert fams["jobs"].samples[0].name == "jobs_total"
def test_special_values_and_timestamps():
text = "a NaN\nb +Inf\nc -Inf\nd 1.5e3 1700000000000\n"
fams = prometheus.parse(text)
assert math.isnan(fams["a"].samples[0].value)
assert fams["b"].samples[0].value == math.inf
assert fams["c"].samples[0].value == -math.inf
assert fams["d"].samples[0].value == 1500.0
def test_malformed_lines_are_skipped_not_fatal():
text = """\
good 1
bad-name 2
unterminated{a="x 3
nolabelvalue{a=} 4
novalue
spaced{a="1"}5
# TYPE weird nonsense
weird 6
good_too{a="b"} 7
"""
fams = prometheus.parse(text)
assert set(fams) == {"good", "weird", "good_too"}
assert fams["weird"].type == "untyped"
assert fams["good_too"].samples[0].value == 7.0
def test_traefik_reads_through_the_shared_parser():
from plugins.traefik.scraper import parse_prometheus
out = parse_prometheus(
'traefik_service_requests_total{service="web@docker",code="200"} 10\n'
)
key = frozenset({("service", "web@docker"), ("code", "200")})
assert out == {"traefik_service_requests_total": {key: 10.0}}