refactor(docker): container CPU and memory live in the one metric store (#5752)
CI / lint (push) Successful in 3s
CI / integration (push) Failing after 1m18s
CI / unit (push) Successful in 1m24s
CI / publish (push) Skipped

#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:
2026-10-14 14:39:56 -04:00
co-authored by Claude Opus 5.5
parent b889584906
commit 5a0e150789
19 changed files with 242 additions and 430 deletions
-45
View File
@@ -1,45 +0,0 @@
"""Unit tests for the docker plugin's retention cutoff/bucketing helpers.
_hour_floor and _rollup_cutoff are pure (no DB); retention.py imports its models
lazily inside run_docker_retention, so importing them here doesn't register ORM
tables — safe for the no-DB unit lane. The DB-backed rollup/prune itself is
covered in tests/integration/test_docker.py.
"""
from datetime import datetime, timedelta, timezone
from plugins.docker.retention import _hour_floor, _rollup_cutoff
def test_hour_floor_drops_sub_hour_components():
dt = datetime(2026, 6, 19, 14, 37, 52, 123456, tzinfo=timezone.utc)
assert _hour_floor(dt) == datetime(2026, 6, 19, 14, 0, 0, 0, tzinfo=timezone.utc)
def test_hour_floor_on_exact_hour_is_identity():
dt = datetime(2026, 6, 19, 14, 0, 0, tzinfo=timezone.utc)
assert _hour_floor(dt) == dt
def test_rollup_cutoff_is_hour_aligned():
now = datetime(2026, 6, 19, 14, 37, 52, tzinfo=timezone.utc)
cutoff = _rollup_cutoff(now, raw_days=7)
# 7 days back from 14:37 is 14:37 on the 12th, floored to 14:00.
assert cutoff == datetime(2026, 6, 12, 14, 0, 0, tzinfo=timezone.utc)
# No sub-hour remainder — only whole elapsed hours get rolled up.
assert cutoff.minute == 0 and cutoff.second == 0 and cutoff.microsecond == 0
def test_rollup_cutoff_scales_with_raw_days():
now = datetime(2026, 6, 19, 0, 0, 0, tzinfo=timezone.utc)
assert _rollup_cutoff(now, 1) == now - timedelta(days=1)
assert _rollup_cutoff(now, 30) == now - timedelta(days=30)
def test_rollup_cutoff_sample_classification():
"""A sample inside the raw window is kept; one before the aligned cutoff rolls up."""
now = datetime(2026, 6, 19, 14, 37, 0, tzinfo=timezone.utc)
cutoff = _rollup_cutoff(now, raw_days=7)
recent = datetime(2026, 6, 19, 12, 0, 0, tzinfo=timezone.utc) # within window
old = datetime(2026, 6, 10, 9, 0, 0, tzinfo=timezone.utc) # older than cutoff
assert not (recent < cutoff) # kept raw
assert old < cutoff # rolled up