fix(traefik): the newest scrape per router comes from plugin_metric_latest (#5355)
The phase log on the live instance: 'traefik rows: slow 7.4s (latest=7.04s history=0.11s window=0.04s ...)'. latest_per_router walked traefik_metrics' index one router at a time; each step lands on the router's oldest entries, which retention deletes every minute (likely dead ones). The scraper already records every router figure through MetricBatch, which keeps one row per router and figure in plugin_metric_latest (#5208's table, read as latest_by_resource does); latest_per_router reads that now, and the router figures carry the scrape's own time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,29 +21,42 @@ def app():
|
||||
return create_app(testing=False)
|
||||
|
||||
|
||||
def test_each_router_reads_as_its_newest_row(app):
|
||||
from plugins.traefik.models import TraefikMetric
|
||||
from plugins.traefik.routes import latest_per_router
|
||||
def test_each_router_reads_as_its_newest_scrape(app):
|
||||
"""#5355: the newest scrape per router comes from plugin_metric_latest,
|
||||
which the scraper's MetricBatch keeps, not from a walk of traefik_metrics
|
||||
(7 s on the live instance). A backlog arriving late doesn't move it back,
|
||||
and a certificate's figure under the same source is not a router."""
|
||||
from steward.core.alerts import MetricBatch
|
||||
from plugins.traefik.routes import ROUTER_METRICS, latest_per_router
|
||||
|
||||
now = datetime.now(timezone.utc).replace(microsecond=0)
|
||||
tag = uuid.uuid4().hex[:8]
|
||||
a, b = f"a-{tag}@docker", f"b-{tag}@docker"
|
||||
a, b, cert = f"a-{tag}@docker", f"b-{tag}@docker", f"cert-{tag}.example"
|
||||
|
||||
def row(name, ago, rate):
|
||||
return TraefikMetric(router_name=name, scraped_at=now - timedelta(minutes=ago), request_rate=rate,
|
||||
error_rate_4xx_pct=0.0, error_rate_5xx_pct=0.0, latency_p50_ms=1.0,
|
||||
latency_p95_ms=2.0, latency_p99_ms=3.0, response_bytes_rate=0.0)
|
||||
def scrape(batch, name, ago, rate):
|
||||
for m in ROUTER_METRICS:
|
||||
batch.add("traefik", name, m, rate if m == "request_rate" else 1.0,
|
||||
recorded_at=now - timedelta(minutes=ago))
|
||||
|
||||
async def go():
|
||||
async with app.db_sessionmaker() as s:
|
||||
for name, ago, rate in [(a, 3, 1.0), (a, 1, 2.0), (b, 5, 4.0), (a, 2, 3.0)]:
|
||||
async with s.begin():
|
||||
batch = MetricBatch()
|
||||
scrape(batch, name, ago, rate)
|
||||
await batch.record(s)
|
||||
async with s.begin():
|
||||
s.add_all([row(a, 3, 1.0), row(a, 1, 2.0), row(a, 2, 3.0), row(b, 5, 4.0)])
|
||||
batch = MetricBatch()
|
||||
batch.add("traefik", cert, "cert_expiry_days", 30.0, recorded_at=now)
|
||||
await batch.record(s)
|
||||
async with s.begin():
|
||||
return {m.router_name: m for m in await latest_per_router(s)}
|
||||
|
||||
got = asyncio.run(go())
|
||||
assert got[a].request_rate == 2.0 and got[a].scraped_at == now - timedelta(minutes=1)
|
||||
assert got[a].latency_p95_ms == 1.0 and got[a].response_bytes_rate == 1.0
|
||||
assert got[b].request_rate == 4.0
|
||||
assert cert not in got
|
||||
|
||||
|
||||
def test_the_request_chart_sums_routers_and_weights_the_5xx_share(app):
|
||||
|
||||
Reference in New Issue
Block a user