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:
+7
-11
@@ -23,8 +23,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Time-series columns pruned at data.retention_days (#5155). A plugin adds its
|
||||
# own with prune_by_age() in setup(), so the core never imports plugin models.
|
||||
# Tables kept another way are not listed: plugin_metrics and the docker tables
|
||||
# are rolled up (below), and the audit log has its own, longer window.
|
||||
# Tables kept another way are not listed: plugin_metrics is rolled up (below),
|
||||
# the docker events and logs have their own windows, and the audit log has its
|
||||
# own, longer window.
|
||||
_AGED: list = [MonitorResult.checked_at, AnsibleRun.started_at, AlertEvent.transitioned_at,
|
||||
EmailOutbox.created_at]
|
||||
|
||||
@@ -40,9 +41,7 @@ def prune_by_age(column) -> None:
|
||||
_METRIC_COUNTS = [("raw_rows_rolled", "plugin_metrics: raw samples rolled into hourly"),
|
||||
("rollup_pruned", "plugin_metrics_hourly: past the rollup window"),
|
||||
("latest_pruned", "plugin_metric_latest: stopped reporting")]
|
||||
_DOCKER_COUNTS = [("raw_rows_rolled", "docker_metrics: raw samples rolled into hourly"),
|
||||
("rollup_pruned", "docker_metrics_hourly: past the rollup window"),
|
||||
("events_pruned", "docker_events: past the events window"),
|
||||
_DOCKER_COUNTS = [("events_pruned", "docker_events: past the events window"),
|
||||
("logs_age_pruned", "docker_logs: past the age window"),
|
||||
("logs_size_pruned", "docker_logs: over a container's size cap")]
|
||||
|
||||
@@ -142,13 +141,13 @@ async def _run_metrics_retention(session, now: datetime) -> dict:
|
||||
|
||||
|
||||
async def _run_docker_retention(session, now: datetime) -> dict | None:
|
||||
"""Drive the docker plugin's rollup + prune and its log ring via its
|
||||
"""Drive the docker plugin's event prune and its log ring via its
|
||||
capabilities, if loaded.
|
||||
|
||||
Windows are read fresh from settings each run (rule 25 — a change in the
|
||||
Settings UI takes effect on the next hourly cleanup, no restart). Each runs
|
||||
in its own transaction, so a docker-side failure can't roll back the generic
|
||||
prune above, and the log ring's deletes don't wait on the metrics rollup
|
||||
prune above, and the log ring's deletes don't wait on the event prune
|
||||
(#5449). No-op when the docker plugin is disabled (capability absent).
|
||||
"""
|
||||
from steward.core.capabilities import has_capability, invoke_capability
|
||||
@@ -160,13 +159,10 @@ async def _run_docker_retention(session, now: datetime) -> dict | None:
|
||||
# Reads + rollup/prune share one transaction — get_setting's SELECT would
|
||||
# otherwise autobegin one, making a later session.begin() raise.
|
||||
async with session.begin():
|
||||
raw_days = int(await get_setting(session, "docker.retention.metrics_raw_days") or 7)
|
||||
rollup_days = int(await get_setting(session, "docker.retention.metrics_rollup_days") or 90)
|
||||
events_days = int(await get_setting(session, "docker.retention.events_days") or 30)
|
||||
counts = await invoke_capability(
|
||||
"docker.run_retention", UserRole.viewer, session,
|
||||
events_days=events_days, metrics_raw_days=raw_days,
|
||||
metrics_rollup_days=rollup_days, now=now,
|
||||
events_days=events_days, now=now,
|
||||
)
|
||||
async with session.begin():
|
||||
logs_days = int(await get_setting(session, "docker.logs.retention_days") or 3)
|
||||
|
||||
@@ -311,6 +311,7 @@ METRICS: dict[str, tuple[str, str]] = {
|
||||
"total_clients": ("Connected clients", "devices on the network"),
|
||||
"cpu_pct": ("CPU", "CPU in use; for a container, 100% is one core"),
|
||||
"mem_pct": ("Memory (% of limit)", "memory in use, against the container's limit or the host's RAM"),
|
||||
"mem_usage_bytes": ("Memory used", "a container's memory in use, in bytes"),
|
||||
"restart_count": ("Restarts", "times the container has restarted, in total"),
|
||||
"disk_total_bytes": ("Docker disk use", "everything Docker keeps on the host (resource: the host)"),
|
||||
"disk_images_bytes": ("Docker images", "space Docker's images take on the host"),
|
||||
|
||||
@@ -117,11 +117,8 @@ DEFAULTS: dict[str, Any] = {
|
||||
# Days an ended metric anomaly waits to be reviewed before it lapses:
|
||||
# it leaves the to-review list and is kept as not reviewed (#5740).
|
||||
"anomalies.review_days": 14,
|
||||
# Docker time-series retention (rule 25 — tunable, no restart). Raw 30s
|
||||
# samples are heavy, so keep a short raw window then roll up to hourly
|
||||
# averages kept much longer; lifecycle events are light, keep a month.
|
||||
"docker.retention.metrics_raw_days": 7,
|
||||
"docker.retention.metrics_rollup_days": 90,
|
||||
# Docker lifecycle events are light; keep a month (rule 25 — tunable, no
|
||||
# restart). Container CPU and memory follow metrics.retention.* (#5752).
|
||||
"docker.retention.events_days": 30,
|
||||
# Container logs (m79): on by default for every container (operator
|
||||
# preference). `exclude` names containers the server drops on ingest; the
|
||||
|
||||
Reference in New Issue
Block a user