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>
99 lines
4.1 KiB
Python
99 lines
4.1 KiB
Python
# plugins/docker/__init__.py
|
|
from __future__ import annotations
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from quart import Quart
|
|
|
|
_app: "Quart | None" = None
|
|
|
|
|
|
def setup(app: "Quart") -> None:
|
|
global _app
|
|
_app = app
|
|
from .models import DockerContainer # noqa: F401 — register with Base
|
|
|
|
# Publish the per-host persist hook so the host agent can hand off the
|
|
# `docker` array from each sample without importing our models (opportunistic
|
|
# synergy — host_agent gates on has_capability and no-ops if we're disabled).
|
|
# required_role=viewer: this is a trusted server-side data-plane write driven
|
|
# by the authenticated agent ingest, not a user-facing privileged action.
|
|
from steward.core.capabilities import register_capability
|
|
from steward.models.users import UserRole
|
|
from .ingest import persist_host_docker
|
|
from .retention import prune_docker_logs, run_docker_retention
|
|
register_capability(
|
|
"docker.persist_host_samples", persist_host_docker,
|
|
label="Persist host Docker samples",
|
|
description="Store per-host container state + metrics pushed by the host agent.",
|
|
required_role=UserRole.viewer,
|
|
)
|
|
# Prune lifecycle events, driven by the core cleanup task without it
|
|
# importing our models. Same trusted server-side data-plane role as above.
|
|
# Container CPU and memory live in the one metric store and its retention.
|
|
register_capability(
|
|
"docker.run_retention", run_docker_retention,
|
|
label="Run Docker retention",
|
|
description="Prune Docker lifecycle events past their window.",
|
|
required_role=UserRole.viewer,
|
|
)
|
|
register_capability(
|
|
"docker.prune_logs", prune_docker_logs,
|
|
label="Prune Docker logs",
|
|
description="Keep each container's logs within the age window and byte cap.",
|
|
required_role=UserRole.viewer,
|
|
)
|
|
# Each container's CPU (100% is one core) and memory share learn their
|
|
# normal (#5742), each with its floor from Settings → Thresholds. Memory
|
|
# is its processes' own, file cache left out (#6030).
|
|
from steward.core.norms import floor_setting, learn_normal
|
|
learn_normal("docker", "cpu_pct", floor_setting(app, "container_cpu_pct"))
|
|
learn_normal("docker", "mem_used_pct", floor_setting(app, "container_mem_pct"))
|
|
# A host's agent carries its containers' figures: they go silent with it (#5721).
|
|
from steward.core.alerts import agent_fed
|
|
agent_fed("docker", lambda resource: resource.split("/", 1)[0])
|
|
|
|
|
|
def get_scheduled_tasks() -> list:
|
|
# Collection is now agent-driven (pushed to the host_agent ingest); the
|
|
# central socket scrape was removed. No periodic task to register.
|
|
return []
|
|
|
|
|
|
def get_blueprint():
|
|
from .routes import docker_bp
|
|
return docker_bp
|
|
|
|
|
|
def register_mcp_tools(register) -> None:
|
|
from .mcp_tools import register_tools
|
|
register_tools(register)
|
|
|
|
|
|
async def mcp_overview(db) -> list[dict]:
|
|
from .mcp_tools import mcp_overview as findings
|
|
return await findings(db)
|
|
|
|
|
|
async def mcp_events(db, host_id: str, host_name: str, since, until) -> list[dict]:
|
|
from .mcp_tools import mcp_events as events
|
|
return await events(db, host_id, host_name, since, until)
|
|
|
|
|
|
def get_nav() -> list[dict]:
|
|
# Surfaces in the sidebar's Infrastructure group (fleet-wide container view).
|
|
return [{"label": "Docker", "href": "/plugins/docker/"}]
|
|
|
|
|
|
def get_alert_metrics() -> dict[str, list[str]]:
|
|
"""What ingest.py records per container (resource "<host>/<container>"):
|
|
restart_count is cumulative (alert on crash-looping); is_healthy is 1.0 /
|
|
0.0 from the container's HEALTHCHECK, only for containers that define one.
|
|
Per host (resource "<host>"), what Docker keeps on its disk (#5304).
|
|
Swarm-wide (resource "swarm"), from any manager's report: services short
|
|
of their replicas, nodes down and managers unreachable, drained nodes
|
|
aside (#5788, swarm_health.py)."""
|
|
from .disk_view import DISK_METRICS
|
|
from .swarm_health import SWARM_METRICS
|
|
return {"docker": ["cpu_pct", "mem_used_pct", "restart_count", "is_healthy", *DISK_METRICS, *SWARM_METRICS]}
|