feat(host_agent): sparklines + load/core + PSI on the host panel
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 8s
CI / integration (push) Successful in 2m23s
CI / publish (push) Successful in 7s

- Each at-a-glance metric (CPU, Memory, Disk /, Load) now shows a 6h sparkline
  (reused core.status.sparkline_svg + a recent-series query; disk uses the root
  mount sub-resource) so trend/consistency is visible, not just the instant.
- Load is now normalized: "Load /core" = 1m load ÷ CPU cores as % (100% = run
  queue matches capacity), comparable across different hardware. Cores derived
  from the per-core CPU metrics already collected — no agent change. Raw load
  in the tooltip.
- Added a "Pressure 10s" line: PSI cpu/mem/io (some, avg10), the
  hardware-independent saturation signal already collected by the agent.

Scribe #898.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 11:10:46 -04:00
parent 36212dc58b
commit a0d1c5f07c
2 changed files with 66 additions and 15 deletions
+27 -14
View File
@@ -14,24 +14,37 @@
</div>
{% if reporting %}
{# ── Reporting: at-a-glance metrics + lifecycle ── #}
<div style="display:flex;gap:2rem;flex-wrap:wrap;margin-bottom:0.9rem;">
{% set cpu = hostlvl.get('cpu_pct') %}
{% set mem = hostlvl.get('mem_used_pct') %}
{% set load1 = hostlvl.get('load_1m') %}
{# ── Reporting: at-a-glance metrics (+ sparkline trend) + lifecycle ── #}
{% set cpu = hostlvl.get('cpu_pct') %}
{% set mem = hostlvl.get('mem_used_pct') %}
{% set lbl = "font-size:0.72rem;color:var(--text-muted);text-transform:uppercase;" %}
<div style="display:flex;gap:1.5rem;flex-wrap:wrap;margin-bottom:0.6rem;">
<div title="Average CPU utilization across all cores">
<div style="font-size:0.72rem;color:var(--text-muted);text-transform:uppercase;">CPU</div>
<div style="font-weight:600;">{{ '%.0f%%'|format(cpu) if cpu is not none else '—' }}</div></div>
<div style="{{ lbl }}">CPU</div>
<div style="font-weight:600;">{{ '%.0f%%'|format(cpu) if cpu is not none else '—' }}</div>
{{ sparks.cpu | safe }}</div>
<div title="RAM in use (total minus available; cache/buffers count as free)">
<div style="font-size:0.72rem;color:var(--text-muted);text-transform:uppercase;">Memory</div>
<div style="font-weight:600;">{{ '%.0f%%'|format(mem) if mem is not none else '—' }}</div></div>
<div style="{{ lbl }}">Memory</div>
<div style="font-weight:600;">{{ '%.0f%%'|format(mem) if mem is not none else '—' }}</div>
{{ sparks.mem | safe }}</div>
<div title="Root filesystem (/) usage — see Full metrics for every mount">
<div style="font-size:0.72rem;color:var(--text-muted);text-transform:uppercase;">Disk /</div>
<div style="font-weight:600;">{{ '%.0f%%'|format(disk_root) if disk_root is not none else '—' }}</div></div>
<div title="System load average over 1 minute (runnable + waiting processes). Compare to CPU core count.">
<div style="font-size:0.72rem;color:var(--text-muted);text-transform:uppercase;">Load 1m</div>
<div style="font-weight:600;">{{ '%.2f'|format(load1) if load1 is not none else '—' }}</div></div>
<div style="{{ lbl }}">Disk /</div>
<div style="font-weight:600;">{{ '%.0f%%'|format(disk_root) if disk_root is not none else '—' }}</div>
{{ sparks.disk | safe }}</div>
<div title="1-minute load average ÷ {{ cores or '?' }} CPU cores (100% = run queue matches capacity). Raw 1m load: {{ '%.2f'|format(load1) if load1 is not none else '—' }}.">
<div style="{{ lbl }}">Load /core</div>
<div style="font-weight:600;">{{ '%d%%'|format(load_per_core) if load_per_core is not none else ('%.2f'|format(load1) if load1 is not none else '—') }}</div>
{{ sparks.load | safe }}</div>
</div>
{% if psi.cpu is not none or psi.mem is not none or psi.io is not none %}
<div style="font-size:0.75rem;color:var(--text-muted);margin-bottom:0.9rem;"
title="Pressure Stall Information — % of the last 10s that tasks were stalled waiting for the resource. Hardware-independent, comparable across hosts.">
<span style="text-transform:uppercase;font-size:0.7rem;letter-spacing:0.04em;color:var(--text-dim);">Pressure 10s</span>
&nbsp;CPU {{ '%.0f%%'|format(psi.cpu) if psi.cpu is not none else '—' }}
· Mem {{ '%.0f%%'|format(psi.mem) if psi.mem is not none else '—' }}
· IO {{ '%.0f%%'|format(psi.io) if psi.io is not none else '—' }}
</div>
{% endif %}
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center;">
<a href="/plugins/host_agent/{{ host.id }}/" class="btn btn-sm btn-ghost">Full metrics →</a>