Files
FabledSteward/plugins/host_agent/__init__.py
T
bvandeusenandClaude Opus 5.5 c7ff5bc556
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m24s
CI / unit (push) Failing after 1m29s
CI / publish (push) Skipped
feat(host_agent): a reboot reads as a reboot, and lost contact as its span (#5756)
#5756 "The agent says when its host rebooted and when it lost contact, so
downtime is recorded as what it was". Agent 1.14.0 posts events with the
next post that gets through: started (with the host's boot time), stopping
(on SIGTERM, with whether systemd says the host is going down) and
lost_contact (from its first unanswered post to the one carrying it).

Steward keeps them as the host's downtime (host_downtime): reboot,
agent_stop, unclean (a boot after the agent was last heard that nobody
announced) or lost_contact. Retried posts apply once.

An announced stop explains the silence for reboot_grace_seconds (Settings
-> Plugins -> host_agent, 10 minutes): seconds_since_seen reads 0 until
then, and the host's other rules go quiet with it (agents_away beside
#5721's registry). Past the grace, agent offline fires.

The host's Agent card lists the last 7 days of downtime. Rows are pruned
at data.retention_days.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:08:16 -04:00

73 lines
3.1 KiB
Python

# plugins/host_agent/__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
# A host's CPU, memory and load per core learn their normal (#5742), each
# with its floor from Settings → Thresholds. Parts don't: a host's CPU has
# a normal, each of its cores doesn't. Disks stay on their fixed cutoffs:
# a nearly full disk is a failure whatever its history (System 182).
from steward.core.norms import floor_setting, learn_normal
learn_normal("host_agent", "cpu_pct", floor_setting(app, "host_cpu_pct"), parts=False)
learn_normal("host_agent", "mem_used_pct", floor_setting(app, "host_mem_pct"), parts=False)
learn_normal("host_agent", "load_pct", floor_setting(app, "host_load_pct"), parts=False)
# "Agent offline" speaks for everything the agent carries: while it is
# firing, the host's other rules going silent isn't emailed (#5721).
from steward.core.alerts import agent_fed, agent_heartbeat
from steward.hosts.service import agent_stale_after_seconds
agent_heartbeat("host_agent", "seconds_since_seen", lambda: agent_stale_after_seconds(app.config))
agent_fed("host_agent", lambda resource: resource.split(":", 1)[0])
# A reboot the agent announced is silence with a reason, for a while (#5756).
from datetime import datetime, timezone
from steward.core.alerts import agents_away
from .downtime import hosts_away, restart_grace_seconds
agents_away(lambda session: hosts_away(session, datetime.now(timezone.utc),
restart_grace_seconds(app.config)))
from steward.core.cleanup import prune_by_age
from .models import HostDowntime
prune_by_age(HostDowntime.started_at)
def get_scheduled_tasks() -> list:
from .scheduler import make_task
return [make_task(_app)]
def get_blueprint():
from .routes import host_agent_bp
return host_agent_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)
def get_alert_metrics() -> dict[str, list[str]]:
"""Host metrics ingest records for alert rules (resource = the host name;
disk_used_pct per mount, resource "<host>:<mount>"). seconds_since_seen
comes from the scheduler, not the agent: "agent offline" is > 180.
clock_off_secs is how far the host's clock is from Steward's, either way
(agent ≥ 1.12.0): "clock drifting" is > 30. load_pct is load_1m as a %
of the host's cores. inodes_used_pct is per mount (agent ≥ 1.13.0), and
disk_used_pct_worst is the fullest mount by space or inodes."""
return {"host_agent": [
"cpu_pct", "mem_used_pct", "mem_available_bytes", "swap_used_bytes",
"disk_used_pct_worst", "disk_used_pct", "inodes_used_pct", "load_1m", "load_5m", "load_15m",
"load_pct",
"uptime_secs", "seconds_since_seen", "clock_off_secs",
]}