refactor(docker): container CPU and memory live in the one metric store (#5752)
#5752 "Container CPU and memory history lives in the one metric store; docker_metrics and docker_metrics_hourly are retired". Container cpu_pct and mem_pct already went to plugin_metrics for alerting; now mem_usage_bytes does too, and every snapshot in an ingest (backlog included) is recorded at its own time. The container history chart, Busiest Containers, a service's history and the MCP container tools read plugin_metrics under resource "<host>/<container>", rolled up and pruned by the shared metric retention. docker_012_drop_metrics drops both tables. Their history is not copied (lesson #5221 "A data migration's cost is set by the live table"); the store already holds 7 days of container CPU and memory %. Docker retention now prunes only lifecycle events, and Settings -> Thresholds has one "Metric retention" card for every source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
from datetime import datetime, timezone
|
||||
from sqlalchemy import (
|
||||
BigInteger, Boolean, DateTime, Float, ForeignKey, Index, Integer, String, Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from steward.models.base import Base
|
||||
@@ -61,69 +60,6 @@ class DockerContainer(Base):
|
||||
blk_write_bytes: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
||||
|
||||
|
||||
class DockerMetric(Base):
|
||||
"""Time-series CPU/memory per container — one row per sample per running
|
||||
container, scoped to the reporting host."""
|
||||
__tablename__ = "docker_metrics"
|
||||
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36), primary_key=True, default=time_ordered_id
|
||||
)
|
||||
host_id: Mapped[str] = mapped_column(
|
||||
String(36), ForeignKey("hosts.id", ondelete="CASCADE"), nullable=False, index=True
|
||||
)
|
||||
container_name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
||||
scraped_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False,
|
||||
default=lambda: datetime.now(timezone.utc),
|
||||
index=True,
|
||||
)
|
||||
cpu_pct: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
||||
mem_pct: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
||||
# BigInteger: per-container memory usage exceeds 2^31 bytes (~2.1 GB) routinely.
|
||||
mem_usage_bytes: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
|
||||
|
||||
# Per-container history lookups filter on (host_id, container_name) then sort
|
||||
# by time — a composite index keeps the rows() sparkline queries cheap.
|
||||
__table_args__ = (
|
||||
Index("ix_docker_metrics_host_container_time",
|
||||
"host_id", "container_name", "scraped_at"),
|
||||
)
|
||||
|
||||
|
||||
class DockerMetricHourly(Base):
|
||||
"""Hourly rollup of docker_metrics — avg cpu/mem per container per hour.
|
||||
|
||||
Raw per-sample rows (~2880/container/day at 30s) are pruned beyond a short
|
||||
window; before deletion they're aggregated here so multi-day history stays
|
||||
cheap to store and query. One row per (host, container, hour bucket); the
|
||||
unique constraint lets retention upsert idempotently if it re-runs before the
|
||||
raw rows are deleted. `bucket` is the hour-truncated sample time.
|
||||
"""
|
||||
__tablename__ = "docker_metrics_hourly"
|
||||
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36), primary_key=True, default=time_ordered_id
|
||||
)
|
||||
host_id: Mapped[str] = mapped_column(
|
||||
String(36), ForeignKey("hosts.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
container_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
bucket: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
cpu_pct: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
||||
mem_pct: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
||||
mem_usage_bytes: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
|
||||
sample_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
|
||||
__table_args__ = (
|
||||
# One bucket per container per host — the conflict target for the
|
||||
# idempotent rollup upsert; doubles as the history-query index.
|
||||
UniqueConstraint("host_id", "container_name", "bucket",
|
||||
name="uq_docker_metrics_hourly_bucket"),
|
||||
Index("ix_docker_metrics_hourly_bucket", "bucket"),
|
||||
)
|
||||
|
||||
|
||||
class DockerEvent(Base):
|
||||
"""Lifecycle events derived by diffing consecutive host snapshots.
|
||||
|
||||
@@ -167,7 +103,7 @@ class DockerLog(Base):
|
||||
"""Container log lines pushed by the host agent — one row per line.
|
||||
|
||||
Time-series, scoped to the reporting host (container names are only unique
|
||||
within a host, same identity as docker_metrics). The agent tails each running
|
||||
within a host, same identity as docker_containers). The agent tails each running
|
||||
container incrementally and folds new lines into its metrics push; `ts` is the
|
||||
line's own Docker timestamp. Bounded by a per-container size+age ring
|
||||
(retention), so a chatty container just keeps a shorter window rather than
|
||||
|
||||
Reference in New Issue
Block a user