82c3d2cf36
First slice of milestone 77 (Docker monitoring depth). Surfaces real per-container
stats beyond basic state, all read-only on the existing push model.
- agent (→1.4.0): collect_docker now inspects each container (health, restart
count, exit code, OOM) and reads net + block I/O from the stats payload; pulls
compose project + swarm service/task/node from container labels. Per-container
inspect+stats calls run over a small bounded ThreadPool so the ~1s-per-stats
blocking doesn't stretch the sample on a busy host.
- schema (docker_003): additive columns on docker_containers — health, exit_code,
oom_killed, compose_project, service_name, task_id, node_id, and BigInteger
net/blk byte counters.
- ingest: persists the enrichment + restart_count (.get keeps older agents working).
- ui: Docker page rows now show health badge, uptime ("up 3d 4h"), restart count,
exit code (+OOM) for stopped containers, and compose/service grouping label.
- tests: agent helpers (grouping, inspect fields, net/IO sum) + collect_docker
assembly incl. inspect; integration asserts enrichment round-trips.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Jg27rgypiW2efULXJDtMC
42 lines
2.1 KiB
Python
42 lines
2.1 KiB
Python
"""Docker container enrichment: health, exit/restart, grouping, I/O counters
|
|
|
|
Adds the fields the agent (≥1.4.0) now reports per container beyond the basic
|
|
state: health status, exit code, OOM flag, compose/swarm grouping labels, and
|
|
cumulative network/block I/O counters. Additive columns — no data loss, so no
|
|
DROP+recreate needed here.
|
|
|
|
Revision ID: docker_003_container_enrichment
|
|
Revises: docker_002_host_scoped
|
|
Create Date: 2026-06-19
|
|
"""
|
|
from typing import Sequence, Union
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision: str = "docker_003_container_enrichment"
|
|
down_revision: Union[str, None] = "docker_002_host_scoped"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.add_column("docker_containers", sa.Column("health", sa.String(16), nullable=True))
|
|
op.add_column("docker_containers", sa.Column("exit_code", sa.Integer, nullable=True))
|
|
op.add_column("docker_containers",
|
|
sa.Column("oom_killed", sa.Boolean, nullable=False, server_default=sa.false()))
|
|
op.add_column("docker_containers", sa.Column("compose_project", sa.String(255), nullable=True))
|
|
op.add_column("docker_containers", sa.Column("service_name", sa.String(255), nullable=True))
|
|
op.add_column("docker_containers", sa.Column("task_id", sa.String(64), nullable=True))
|
|
op.add_column("docker_containers", sa.Column("node_id", sa.String(64), nullable=True))
|
|
op.add_column("docker_containers", sa.Column("net_rx_bytes", sa.BigInteger, nullable=True))
|
|
op.add_column("docker_containers", sa.Column("net_tx_bytes", sa.BigInteger, nullable=True))
|
|
op.add_column("docker_containers", sa.Column("blk_read_bytes", sa.BigInteger, nullable=True))
|
|
op.add_column("docker_containers", sa.Column("blk_write_bytes", sa.BigInteger, nullable=True))
|
|
|
|
|
|
def downgrade() -> None:
|
|
for col in ("blk_write_bytes", "blk_read_bytes", "net_tx_bytes", "net_rx_bytes",
|
|
"node_id", "task_id", "service_name", "compose_project",
|
|
"oom_killed", "exit_code", "health"):
|
|
op.drop_column("docker_containers", col)
|