fix(docker): a container's memory is its processes' own; file cache shown on its page (#6030)
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:
+26
-14
@@ -22,7 +22,7 @@ from collections import deque
|
||||
from concurrent.futures import ThreadPoolExecutor, wait as wait_futures
|
||||
from datetime import datetime, timezone
|
||||
|
||||
AGENT_VERSION = "1.22.0"
|
||||
AGENT_VERSION = "1.23.0"
|
||||
|
||||
# Default path to the local Docker Engine socket. Overridable via the
|
||||
# `docker_socket` config key; collection is silently skipped if it's absent or
|
||||
@@ -721,19 +721,30 @@ def _docker_cpu_pct(stats: dict) -> float:
|
||||
|
||||
|
||||
def _docker_mem(stats: dict):
|
||||
"""Return (usage_bytes, limit_bytes, mem_pct); working set excludes cache."""
|
||||
"""Return (used_bytes, limit_bytes, used_pct, cache_bytes) (#6030).
|
||||
|
||||
Used is what the processes hold and can't hand back without swap: anon
|
||||
plus shmem (Postgres keeps its shared buffers in shmem). File cache is
|
||||
reported apart: the kernel keeps what a container read or wrote while
|
||||
nothing else wants the memory, so a file-heavy container's cache can
|
||||
run to hundreds of GB it doesn't need. Docker's own working set (usage
|
||||
less inactive_file) still counts the recently used part of it."""
|
||||
try:
|
||||
mem = stats.get("memory_stats", {})
|
||||
usage = mem.get("usage", 0)
|
||||
limit = mem.get("limit", 0)
|
||||
# Subtract page cache for working set (cgroup v1 "cache", v2 "inactive_file").
|
||||
cache = mem.get("stats", {}).get("cache", 0) or \
|
||||
mem.get("stats", {}).get("inactive_file", 0)
|
||||
actual = max(0, usage - cache)
|
||||
pct = round(actual / limit * 100.0, 2) if limit > 0 else 0.0
|
||||
return actual, limit, pct
|
||||
st = mem.get("stats", {})
|
||||
if "anon" in st: # cgroup v2
|
||||
shmem = st.get("shmem", 0)
|
||||
used = st["anon"] + shmem
|
||||
cache = max(0, st.get("file", 0) - shmem)
|
||||
else: # cgroup v1: rss leaves shmem out, cache has it in
|
||||
shmem = st.get("total_shmem", st.get("shmem", 0))
|
||||
used = st.get("total_rss", st.get("rss", 0)) + shmem
|
||||
cache = max(0, st.get("total_cache", st.get("cache", 0)) - shmem)
|
||||
pct = round(used / limit * 100.0, 2) if limit > 0 else 0.0
|
||||
return used, limit, pct, cache
|
||||
except (KeyError, TypeError):
|
||||
return 0, 0, 0.0
|
||||
return 0, 0, 0.0, 0
|
||||
|
||||
|
||||
def _docker_ports(ports: list) -> list:
|
||||
@@ -832,13 +843,13 @@ def _collect_one_container(socket_path: str, c: dict) -> dict:
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
|
||||
cpu_pct = mem_usage = mem_limit = mem_pct = None
|
||||
cpu_pct = mem_used = mem_limit = mem_pct = mem_cache = None
|
||||
net_rx = net_tx = blk_read = blk_write = None
|
||||
if state == "running":
|
||||
try:
|
||||
stats = _docker_request(socket_path, f"/containers/{cid}/stats?stream=false")
|
||||
cpu_pct = _docker_cpu_pct(stats)
|
||||
mem_usage, mem_limit, mem_pct = _docker_mem(stats)
|
||||
mem_used, mem_limit, mem_pct, mem_cache = _docker_mem(stats)
|
||||
net_rx, net_tx, blk_read, blk_write = _docker_net_io(stats)
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
@@ -853,9 +864,10 @@ def _collect_one_container(socket_path: str, c: dict) -> dict:
|
||||
"image": c.get("Image", ""),
|
||||
"status": state,
|
||||
"cpu_pct": cpu_pct,
|
||||
"mem_usage_bytes": mem_usage,
|
||||
"mem_used_bytes": mem_used,
|
||||
"mem_limit_bytes": mem_limit,
|
||||
"mem_pct": mem_pct,
|
||||
"mem_used_pct": mem_pct,
|
||||
"mem_cache_bytes": mem_cache,
|
||||
"ports": _docker_ports(c.get("Ports", [])),
|
||||
"started_at": started_at,
|
||||
"finished_at": finished_at,
|
||||
|
||||
Reference in New Issue
Block a user