From aacfcd420e8f4b6e9f12ecea070f4e7abe7b5bb8 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 18 Mar 2026 18:38:54 -0400 Subject: [PATCH] feat: traefik scrape task (delta tracking, DB write, record_metric) --- plugins/traefik/scheduler.py | 97 ++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 plugins/traefik/scheduler.py diff --git a/plugins/traefik/scheduler.py b/plugins/traefik/scheduler.py new file mode 100644 index 0000000..f47404a --- /dev/null +++ b/plugins/traefik/scheduler.py @@ -0,0 +1,97 @@ +# plugins/traefik/scheduler.py +from __future__ import annotations +import logging +import time +from typing import TYPE_CHECKING + +from fablednetmon.core.scheduler import ScheduledTask + +if TYPE_CHECKING: + from quart import Quart + +logger = logging.getLogger(__name__) + +# Module-level state: previous scrape data for delta computation +_prev_metrics: dict = {} +_prev_time: float = 0.0 + + +def make_scrape_task(app: "Quart") -> ScheduledTask: + """Return a ScheduledTask that scrapes Traefik metrics on the configured interval.""" + interval = app.config["PLUGINS"]["traefik"]["scrape_interval_seconds"] + + async def scrape() -> None: + await _do_scrape(app) + + return ScheduledTask( + name="traefik_scrape", + coro_factory=scrape, + interval_seconds=int(interval), + run_on_startup=True, + ) + + +async def _do_scrape(app: "Quart") -> None: + """Fetch Traefik metrics, write to DB, and emit plugin_metrics.""" + global _prev_metrics, _prev_time + + from datetime import datetime, timezone + from plugins.traefik.models import TraefikMetric + from plugins.traefik.scraper import fetch_metrics, compute_router_metrics + from fablednetmon.core.alerts import record_metric + + metrics_url: str = app.config["PLUGINS"]["traefik"]["metrics_url"] + + try: + current = await fetch_metrics(metrics_url) + except Exception: + logger.exception("Traefik scrape failed (url=%s)", metrics_url) + return + + now = time.monotonic() + elapsed = now - _prev_time if _prev_time else 60.0 + router_metrics = compute_router_metrics(current, _prev_metrics or None, elapsed) + + _prev_metrics = current + _prev_time = now + + if not router_metrics: + logger.debug("Traefik scrape: no router metrics found") + return + + scraped_at = datetime.now(timezone.utc) + + async with app.db_sessionmaker() as session: + async with session.begin(): + for router_name, metrics in router_metrics.items(): + # Write to traefik_metrics history table + row = TraefikMetric( + router_name=router_name, + scraped_at=scraped_at, + request_rate=metrics["request_rate"], + error_rate_4xx_pct=metrics["error_rate_4xx_pct"], + error_rate_5xx_pct=metrics["error_rate_5xx_pct"], + latency_p50_ms=metrics["latency_p50_ms"], + latency_p95_ms=metrics["latency_p95_ms"], + latency_p99_ms=metrics["latency_p99_ms"], + ) + session.add(row) + + # Emit plugin_metrics for alert rules + for metric_name, value in [ + ("request_rate", metrics["request_rate"]), + ("error_rate_4xx_pct", metrics["error_rate_4xx_pct"]), + ("error_rate_5xx_pct", metrics["error_rate_5xx_pct"]), + ("latency_p50_ms", metrics["latency_p50_ms"]), + ("latency_p95_ms", metrics["latency_p95_ms"]), + ("latency_p99_ms", metrics["latency_p99_ms"]), + ]: + await record_metric( + session=session, + source_module="traefik", + resource_name=router_name, + metric_name=metric_name, + value=value, + ) + + logger.debug("Traefik scrape: wrote metrics for %d router(s)", len(router_metrics))