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>
72 lines
3.6 KiB
Python
72 lines
3.6 KiB
Python
# plugins/docker/swarm_nodes.py
|
|
"""The Swarm page laid out by node (#5303): one card per node with its role,
|
|
whether it takes work, what its containers use against what the machine has,
|
|
and the services Swarm placed on it. Model-free, so it is unit-tested without
|
|
a database; routes.swarm() feeds it.
|
|
|
|
A node with a Steward agent reports its own containers, so its services carry
|
|
CPU and memory. A node without one is known only from the managers' placement:
|
|
its services are listed by count, with no figures.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from .swarm_view import _placement
|
|
|
|
|
|
def node_ok(n) -> bool:
|
|
"""A node that is up and taking work."""
|
|
return n.status == "ready" and n.availability == "active"
|
|
|
|
|
|
def node_cards(nodes, services, containers, *, node_host: dict, host_names: dict,
|
|
capacity: dict, service_state: dict) -> list[dict]:
|
|
"""One card per node, a node in trouble first, then managers, then by name.
|
|
|
|
`nodes` and `services` are one swarm's deduplicated rows; `containers`
|
|
every container row (only running swarm tasks on these nodes count);
|
|
`node_host` maps a node id to the Steward host on it, `capacity` a host id
|
|
to (cores, memory bytes), `service_state` a service name to healthy,
|
|
degraded or down."""
|
|
running_on: dict[str, dict[str, dict]] = {}
|
|
for c in containers:
|
|
if not (c.node_id and c.service_name and c.status == "running"):
|
|
continue
|
|
row = running_on.setdefault(c.node_id, {}).setdefault(
|
|
c.service_name, {"name": c.service_name, "tasks": 0, "cpu_pct": 0.0, "mem_bytes": 0,
|
|
"measured": True})
|
|
row["tasks"] += 1
|
|
row["cpu_pct"] += c.cpu_pct or 0.0
|
|
row["mem_bytes"] += c.mem_used_bytes or 0
|
|
placed: dict[str, dict[str, int]] = {}
|
|
for s in services:
|
|
for p in _placement(s):
|
|
count = int(p.get("running") or 0)
|
|
if count > 0 and p.get("node_id"):
|
|
placed.setdefault(p["node_id"], {})[s.service_name] = count
|
|
|
|
cards = []
|
|
for n in nodes:
|
|
rows = dict(running_on.get(n.node_id, {}))
|
|
for name, count in placed.get(n.node_id, {}).items():
|
|
rows.setdefault(name, {"name": name, "tasks": count, "cpu_pct": None, "mem_bytes": None,
|
|
"measured": False})
|
|
for r in rows.values():
|
|
r["state"] = service_state.get(r["name"], "healthy")
|
|
r["cores"] = r["cpu_pct"] / 100 if r["cpu_pct"] is not None else None
|
|
tasks = sorted(rows.values(), key=lambda r: (r["state"] == "healthy", -(r["cores"] or 0.0),
|
|
r["name"].lower()))
|
|
host_id = node_host.get(n.node_id)
|
|
cores, mem = capacity.get(host_id, (None, None)) if host_id else (None, None)
|
|
measured = [r for r in tasks if r["measured"]]
|
|
cards.append({
|
|
"node_id": n.node_id, "hostname": n.hostname or n.node_id[:12], "role": n.role,
|
|
"leader": n.leader, "status": n.status, "availability": n.availability, "ok": node_ok(n),
|
|
"host_id": host_id, "host_name": host_names.get(host_id) if host_id else None,
|
|
"agent": bool(running_on.get(n.node_id)) or host_id is not None,
|
|
"tasks": tasks, "task_count": sum(r["tasks"] for r in tasks),
|
|
"cores_used": sum(r["cores"] for r in measured) if measured else None,
|
|
"mem_used": sum(r["mem_bytes"] for r in measured) if measured else None,
|
|
"cores": cores, "mem": mem,
|
|
})
|
|
return sorted(cards, key=lambda c: (c["ok"], c["role"] != "manager", c["hostname"].lower()))
|