fix(docker): a container's memory is its processes' own; file cache shown on its page (#6030)
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m29s
CI / integration (push) Successful in 1m45s
CI / publish (push) Successful in 1m43s

The agent sent Docker's working set (usage less inactive_file), which still
counts recently used file cache. A file-heavy container with no limit read
191 GB while its processes held 2.2 GB, and the host's own figure, which
already leaves cache out, disagreed with it.

Agent 1.23.0 sends mem_used_bytes / mem_used_pct (anon + shmem, so
Postgres's shared buffers count; v1: rss + shmem) and mem_cache_bytes
apart. The new names keep the two meanings apart in history and in learned
normals: docker_013 renames the columns, clears the old values and adds
mem_cache_bytes. The Docker page, swarm by-node sums, Busiest Containers,
container detail, normals and the MCP tools all read the new figure. File
cache is shown only on the container's page and in get_container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 21:27:29 -04:00
co-authored by Claude Opus 5.5
parent 1335b9afaf
commit 56063f27a4
26 changed files with 226 additions and 99 deletions
+6 -5
View File
@@ -345,8 +345,8 @@ async def persist_host_docker(session, host, snapshots, swarm=None, disk=None,
`snapshots` is a list of (recorded_at: datetime, containers: list[dict]) —
one entry per ingested sample carrying docker data (usually one; more when
the agent flushes a backlog). Every snapshot's running containers record
cpu_pct, mem_pct and mem_usage_bytes into the one metric store, at the
snapshot's time; the newest snapshot drives current container state and
cpu_pct, mem_used_pct, mem_used_bytes and mem_cache_bytes into the one
metric store, at the snapshot's time; the newest snapshot drives current container state and
lifecycle-event derivation. `swarm` is the newest
sample's swarm object (or None off managers) — persisted when present.
`disk` is the newest sample's /system/df summary (or None on Docker-less
@@ -400,7 +400,7 @@ async def persist_host_docker(session, host, snapshots, swarm=None, disk=None,
if c.get("status") != "running" or c.get("cpu_pct") is None or not c.get("name"):
continue
resource = f"{host.name}/{c['name']}"
for metric in ("cpu_pct", "mem_pct", "mem_usage_bytes"):
for metric in ("cpu_pct", "mem_used_pct", "mem_used_bytes", "mem_cache_bytes"):
if c.get(metric) is not None:
batch.add(source_module="docker", resource_name=resource,
metric_name=metric, value=c[metric], recorded_at=recorded_at)
@@ -444,9 +444,10 @@ async def persist_host_docker(session, host, snapshots, swarm=None, disk=None,
existing.image = c.get("image", "") or ""
existing.status = c.get("status", "unknown") or "unknown"
existing.cpu_pct = c.get("cpu_pct")
existing.mem_usage_bytes = c.get("mem_usage_bytes")
existing.mem_used_bytes = c.get("mem_used_bytes")
existing.mem_limit_bytes = c.get("mem_limit_bytes")
existing.mem_pct = c.get("mem_pct")
existing.mem_used_pct = c.get("mem_used_pct")
existing.mem_cache_bytes = c.get("mem_cache_bytes")
existing.ports_json = json.dumps(c.get("ports") or [])
existing.started_at = _parse_started_at(c.get("started_at"))
existing.scraped_at = latest_at