Files
FabledSteward/plugins/prometheus/models.py
T
bvandeusenandClaude Opus 5.5 3cb2a95054
CI / lint (push) Successful in 2s
CI / integration (push) Failing after 1m12s
CI / unit (push) Failing after 1m27s
CI / publish (push) Skipped
feat(prometheus): scrape any /metrics endpoint into Steward's metrics, with pages (#5668, #5669)
A bundled prometheus plugin, on by default like the other protocol plugins.
Targets are rows edited on its page: address, bearer token (encrypted),
interval, keep/leave-out name patterns and a series cap. Steward does the
filtering; the endpoint serves everything (Archives' ruling).

- A 5 s tick scrapes due targets at once, each within a 30 s deadline, and
  writes the outcome to its row and its series through record_metrics, so
  alert rules can watch any of them.
- Gauges are stored as they are; counters (and a histogram's or summary's
  _sum/_count) as <name>_per_sec rates from the second scrape. Buckets and
  quantiles are skipped. A labelled series is stored under
  "<target>:k=v,…". Each target also records is_up, scrape_ms, series_kept
  and series_capped.
- Pages: targets not answering first, with why; a target's series grouped
  by metric with their newest values; a chart per series; the editor with
  Test, which scrapes once and says what would be stored.
- The counter-to-rate keeper moves to steward/core/counter_rates.py; SNMP's
  RateKeeper now scales its rate on top of it.
- metric_value reads Prometheus's _seconds as a span and _per_sec as a rate.
- The catalog guard treats an empty list as a dynamic source (snmp,
  prometheus), as the rule form does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:04:01 -04:00

42 lines
2.2 KiB
Python

# plugins/prometheus/models.py
from __future__ import annotations
from datetime import datetime, timezone
from sqlalchemy import JSON, Boolean, DateTime, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from steward.core.ids import time_ordered_id
from steward.models.base import Base
class PrometheusTarget(Base):
"""A /metrics endpoint Steward scrapes, edited on the Prometheus page.
The bearer token is stored encrypted. keep and drop are lists of name
patterns ("fabledarchives_*") an admin chooses: Steward does the
filtering, the endpoint serves everything. The last_* columns are the
newest scrape's outcome, written by the scraper, so a page can say why a
target isn't answering without asking it.
"""
__tablename__ = "prometheus_targets"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=time_ordered_id)
name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True)
url: Mapped[str] = mapped_column(String(500), nullable=False)
token: Mapped[str] = mapped_column(Text, nullable=False, default="")
verify_tls: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
interval_seconds: Mapped[int] = mapped_column(Integer, nullable=False, default=60)
keep: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
drop: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
series_cap: Mapped[int] = mapped_column(Integer, nullable=False, default=1000)
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc))
last_scraped_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
last_ok_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
last_kept: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
last_dropped: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
last_capped: Mapped[int] = mapped_column(Integer, nullable=False, default=0)