"""A router's latency is each scrape's own percentile (#5741): the rise in Traefik's histogram since the last scrape, every label set's buckets summed per `le`. Read as it stood, the histogram gave a percentile of every response since Traefik started, which barely moved (nextcloud's flat 100 ms p95).""" import pytest from plugins.traefik.scraper import compute_router_metrics, service_buckets BUCKET = "traefik_service_request_duration_seconds_bucket" EDGES = ("0.1", "0.3", "1.2", "5", "+Inf") # Traefik's default buckets def _hist(service, code, method, counts): """{labels: count} for one label set's cumulative buckets, in EDGES order.""" return {frozenset({"service": service, "code": code, "method": method, "le": le}.items()): n for le, n in zip(EDGES, counts)} def _scrape(*sets): out = {} for s in sets: out.update(_hist(*s)) return {BUCKET: out} def test_latency_reads_the_responses_since_the_last_scrape_not_since_start(): # A million fast responses since start, then 100 slow ones (all 0.3–1.2 s). before = _scrape(("cloud@docker", "200", "GET", (1_000_000, 1_000_000, 1_000_000, 1_000_000, 1_000_000))) after = _scrape(("cloud@docker", "200", "GET", (1_000_000, 1_000_000, 1_000_100, 1_000_100, 1_000_100))) got = compute_router_metrics(after, before, 60.0)["cloud@docker"] assert 300.0 < got["latency_p50_ms"] < 1200.0 assert got["latency_p95_ms"] == pytest.approx(300.0 + 0.95 * 900.0) def test_every_label_set_counts_toward_one_histogram(): # GET answered fast and POST slowly: 90 + 10 responses, so p95 is a POST's. before = _scrape(("app@docker", "200", "GET", (0, 0, 0, 0, 0)), ("app@docker", "200", "POST", (0, 0, 0, 0, 0))) after = _scrape(("app@docker", "200", "GET", (90, 90, 90, 90, 90)), ("app@docker", "200", "POST", (0, 0, 0, 10, 10))) assert service_buckets(after[BUCKET], before[BUCKET], "app@docker") == [ (0.1, 90.0), (0.3, 90.0), (1.2, 90.0), (5.0, 100.0), (float("inf"), 100.0)] got = compute_router_metrics(after, before, 60.0)["app@docker"] assert got["latency_p50_ms"] < 100.0 assert 1200.0 < got["latency_p95_ms"] < 5000.0 def test_an_idle_scrape_and_the_first_scrape_have_no_latency(): still = _scrape(("idle@docker", "200", "GET", (50, 60, 60, 60, 60))) idle = compute_router_metrics(still, still, 60.0)["idle@docker"] first = compute_router_metrics(still, None, 60.0)["idle@docker"] for got in (idle, first): assert got["latency_p50_ms"] is None and got["latency_p95_ms"] is None assert got["latency_p99_ms"] is None def test_a_percentile_past_the_last_edge_reads_as_at_least_that_edge(): before = _scrape(("ws@docker", "101", "GET", (0, 0, 0, 0, 0))) after = _scrape(("ws@docker", "101", "GET", (0, 0, 0, 0, 20))) # all longer than 5 s assert compute_router_metrics(after, before, 60.0)["ws@docker"]["latency_p95_ms"] == 5000.0