Files
FabledSteward/plugins/host_agent/templates/_host_senders.html
T
bvandeusenandClaude Opus 5.5 26bc57ce04
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m29s
CI / integration (push) Successful in 1m39s
CI / publish (push) Successful in 1m4s
feat(traffic): a host's top senders, on its page and as get_host_senders (#6115)
The host page gains a "Who's sending" panel, and the MCP a tool, that
put the host's outbound (or inbound) traffic down to its containers and
NFS mounts: read in shared time buckets, so the host's busiest bucket
says who was sending in it. A swarm service's tasks add up under its
name; what none of them accounts for is the host itself.

Also fixes CI 9488: per-container net_rx_bps / net_tx_bps join the
docker alert catalog, so a rule can catch a container flooding the
uplink.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:38:28 -04:00

39 lines
2.1 KiB
HTML

{# Who sent the host's traffic over the range (#6115): its containers and NFS
mounts at the host's busiest moment, biggest first. Lazy-loaded below the
charts and refreshed with them. #}
<div class="section-title" style="margin-bottom:0.5rem;">Who's sending — last {{ range_key }}</div>
{% if not got.host %}
<p class="field-hint" style="margin:0;">No outbound traffic recorded for this host in the last {{ range_key }}.</p>
{% else %}
{% set top = got.senders[0] if got.senders else None %}
<p style="margin:0 0 0.6rem;">
Busiest at {{ got.host.peak_at|when }}: <strong>{{ got.host.peak_bps|rate }}</strong> out (average {{ got.host.avg_bps|rate }}).
{% if top and top.at_host_peak_bps >= got.unaccounted_at_peak_bps %}Most of it from <strong>{{ top.name }}</strong> ({{ top.at_host_peak_bps|rate }}).
{% else %}Most of it from <strong>no container or NFS mount</strong>: the host itself.{% endif %}
</p>
<table class="table">
<thead><tr><th>Sender</th><th>At the peak</th><th>Average</th><th>Its own peak</th></tr></thead>
<tbody>
{% for r in got.senders %}
<tr>
<td><span title="{{ r.name }}">{{ "NFS " if r.kind == "nfs" }}{{ r.name }}</span></td>
<td style="font-variant-numeric:tabular-nums;">{{ r.at_host_peak_bps|rate }}</td>
<td style="font-variant-numeric:tabular-nums;">{{ r.avg_bps|rate }}</td>
<td style="font-variant-numeric:tabular-nums;">{{ r.peak_bps|rate }} · {{ r.peak_at|when }}</td>
</tr>
{% endfor %}
<tr>
<td style="color:var(--text-muted);">The host itself, or none of the above</td>
<td style="font-variant-numeric:tabular-nums;">{{ got.unaccounted_at_peak_bps|rate }}</td>
<td style="font-variant-numeric:tabular-nums;">{{ got.unaccounted_avg_bps|rate }}</td>
<td>—</td>
</tr>
</tbody>
</table>
<p class="field-hint" style="margin:0.5rem 0 0;">
Read in {{ (got.bucket_s / 60)|round|int }}-minute steps; a shorter range shows a spike sharper.
{% if got.more %}{{ got.more }} more sent less. {% endif %}
A container's traffic to another on this host counts for it without leaving the host.
</p>
{% endif %}