faecac3ec6
Bounds Docker time-series growth (the main scaling concern). New docker_metrics_hourly table + docker_006 migration; a plugin retention module (docker.run_retention capability) rolls raw docker_metrics older than the raw window into hourly averages (idempotent upsert), deletes the rolled raw rows, then prunes stale rollups + lifecycle events. Core cleanup.py drives it each hourly run via the capability (no plugin-model import), reading the three retention windows fresh from settings so changes apply without restart (rule 25). Settings → "Thresholds & Retention" gains a Docker retention card (raw / rolled-up / events windows, working defaults 7/90/30 days). Unit tests cover the hour-aligned cutoff/bucketing helpers; integration test exercises the real rollup-average + prune across both windows. Milestone 77 task #941. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Jg27rgypiW2efULXJDtMC
46 lines
1.9 KiB
Python
46 lines
1.9 KiB
Python
"""Docker hourly metric rollup table
|
|
|
|
Adds docker_metrics_hourly — the coarse series that retention rolls raw
|
|
docker_metrics into before pruning them, so multi-day history stays cheap.
|
|
One row per (host, container, hour bucket); the unique constraint is the
|
|
conflict target for the idempotent rollup upsert. Additive create_table.
|
|
|
|
Revision ID: docker_006_metric_rollup
|
|
Revises: docker_005_swarm_placement
|
|
Create Date: 2026-06-19
|
|
"""
|
|
from typing import Sequence, Union
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision: str = "docker_006_metric_rollup"
|
|
down_revision: Union[str, None] = "docker_005_swarm_placement"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"docker_metrics_hourly",
|
|
sa.Column("id", sa.String(length=36), nullable=False),
|
|
sa.Column("host_id", sa.String(length=36), nullable=False),
|
|
sa.Column("container_name", sa.String(length=255), nullable=False),
|
|
sa.Column("bucket", sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column("cpu_pct", sa.Float(), nullable=False, server_default="0"),
|
|
sa.Column("mem_pct", sa.Float(), nullable=False, server_default="0"),
|
|
sa.Column("mem_usage_bytes", sa.BigInteger(), nullable=False, server_default="0"),
|
|
sa.Column("sample_count", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.ForeignKeyConstraint(["host_id"], ["hosts.id"], ondelete="CASCADE"),
|
|
sa.PrimaryKeyConstraint("id"),
|
|
sa.UniqueConstraint("host_id", "container_name", "bucket",
|
|
name="uq_docker_metrics_hourly_bucket"),
|
|
)
|
|
op.create_index("ix_docker_metrics_hourly_bucket",
|
|
"docker_metrics_hourly", ["bucket"])
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("ix_docker_metrics_hourly_bucket",
|
|
table_name="docker_metrics_hourly")
|
|
op.drop_table("docker_metrics_hourly")
|