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:
@@ -16,7 +16,7 @@ NOW = datetime.now(timezone.utc)
|
||||
|
||||
def _c(name, **kw):
|
||||
base = dict(name=name, host_id="h1", image="nginx:1", status="running", health=None, exit_code=None,
|
||||
oom_killed=False, restart_count=0, cpu_pct=5.0, mem_pct=10.0, compose_project=None,
|
||||
oom_killed=False, restart_count=0, cpu_pct=5.0, mem_used_pct=10.0, compose_project=None,
|
||||
service_name=None, ports_json=None, started_at=NOW - timedelta(hours=2),
|
||||
scraped_at=NOW - timedelta(seconds=20))
|
||||
base.update(kw)
|
||||
|
||||
@@ -15,8 +15,8 @@ NOW = datetime(2026, 10, 6, 12, 0, tzinfo=timezone.utc)
|
||||
|
||||
def _c(**kw):
|
||||
base = dict(name="web", host_id="h1", image="nginx", status="running", health=None,
|
||||
exit_code=None, oom_killed=False, restart_count=0, cpu_pct=1.0, mem_pct=2.0,
|
||||
mem_usage_bytes=1, mem_limit_bytes=2, started_at=NOW - timedelta(hours=1),
|
||||
exit_code=None, oom_killed=False, restart_count=0, cpu_pct=1.0, mem_used_pct=2.0,
|
||||
mem_used_bytes=1, mem_limit_bytes=2, started_at=NOW - timedelta(hours=1),
|
||||
compose_project=None, service_name=None, scraped_at=NOW - timedelta(seconds=30))
|
||||
base.update(kw)
|
||||
return NS(**base)
|
||||
|
||||
@@ -265,7 +265,7 @@ def _ctr(**kw):
|
||||
base = dict(name="plex", host_id="h1", image="plexinc/pms:1.40", status="exited", health=None,
|
||||
exit_code=137, oom_killed=True, restart_count=2, compose_project="media",
|
||||
service_name=None, node_id=None, scraped_at=NOW - timedelta(seconds=30),
|
||||
started_at=NOW - timedelta(hours=3))
|
||||
started_at=NOW - timedelta(hours=3), mem_cache_bytes=None)
|
||||
base.update(kw)
|
||||
return NS(**base)
|
||||
|
||||
@@ -312,6 +312,18 @@ async def test_a_running_swarm_task_reads_under_its_service(app):
|
||||
assert "<dt>Swarm node</dt>" in page and "docker02" in page
|
||||
|
||||
|
||||
async def test_file_cache_is_told_on_the_container_page_apart_from_its_memory(app):
|
||||
"""#6030: its memory figure is what its processes hold; the file cache
|
||||
charged to it is shown here, said to be given back, and is "—" while
|
||||
nothing reports it."""
|
||||
running = _ctr(status="running", exit_code=0, oom_killed=False, mem_cache_bytes=180 << 30)
|
||||
page = await _container_page(app, running, exit_failed=False, uptime="3h")
|
||||
assert "<dt>File cache</dt><dd>180.0 GiB" in page
|
||||
assert "kept in memory until the host needs it back; not counted in its memory" in page
|
||||
stopped = await _container_page(app, _ctr())
|
||||
assert '<dt>File cache</dt><dd><span class="tone-dim">—</span></dd>' in stopped
|
||||
|
||||
|
||||
async def test_a_service_log_line_names_its_task(app):
|
||||
lines = [{"ts": NOW, "stream": "stderr", "line": "<b>boom</b>", "task": f"1 · {TASK[:10]}"}]
|
||||
async with app.test_request_context("/plugins/docker/service/web/logs/lines"):
|
||||
@@ -335,7 +347,7 @@ def _swarm_svc(name, placement):
|
||||
|
||||
def _task(name, service, node, cpu, mem, status="running"):
|
||||
return NS(name=name, service_name=service, node_id=node, status=status, cpu_pct=cpu,
|
||||
mem_usage_bytes=mem)
|
||||
mem_used_bytes=mem)
|
||||
|
||||
|
||||
def test_each_node_lists_what_runs_on_it_and_what_that_uses():
|
||||
|
||||
@@ -81,12 +81,12 @@ def test_task_label_names_the_service_and_slot():
|
||||
|
||||
def test_usage_reads_cpu_in_cores_and_memory_against_a_real_limit():
|
||||
gib = 1024 ** 3
|
||||
c = NS(cpu_pct=150.0, mem_usage_bytes=gib, mem_limit_bytes=16 * gib, mem_pct=6.25)
|
||||
c = NS(cpu_pct=150.0, mem_used_bytes=gib, mem_limit_bytes=16 * gib, mem_used_pct=6.25)
|
||||
u = svc.usage(c, 4, 16 * gib)
|
||||
assert u["cores"] == 1.5 and u["cpu_share"] == 37.5
|
||||
# A limit of the host's whole memory is Docker saying "no limit".
|
||||
assert u["mem_limit"] is None and u["mem_of"] == "the host's memory"
|
||||
limited = svc.usage(NS(cpu_pct=None, mem_usage_bytes=gib, mem_limit_bytes=2 * gib, mem_pct=50.0), 4, 16 * gib)
|
||||
limited = svc.usage(NS(cpu_pct=None, mem_used_bytes=gib, mem_limit_bytes=2 * gib, mem_used_pct=50.0), 4, 16 * gib)
|
||||
assert limited["cores"] is None and limited["mem_limit"] == 2 * gib and limited["mem_of"] == "its limit"
|
||||
# Unknown host size: cores still read, no share to grade against.
|
||||
assert svc.usage(c, None, None)["cpu_share"] is None
|
||||
|
||||
@@ -17,7 +17,7 @@ NOW = datetime(2026, 10, 6, 13, 30, tzinfo=timezone.utc)
|
||||
def _c(host_id, name, *, service=None, node=None, status="running", cpu=1.0,
|
||||
task=None, started=None):
|
||||
return SimpleNamespace(host_id=host_id, name=name, service_name=service,
|
||||
node_id=node, status=status, cpu_pct=cpu, mem_pct=2.0,
|
||||
node_id=node, status=status, cpu_pct=cpu, mem_used_pct=2.0,
|
||||
health=None, restart_count=0, container_id=name,
|
||||
task_id=task, started_at=started)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user