feat(docker): Busiest Containers shows bars of the last hours beside each figure, scaled to the row's peak with a floor (#5214)
CI / lint (push) Successful in 4s
CI / integration (push) Failing after 59s
CI / unit (push) Failing after 1m27s
CI / publish (push) Skipped

The fill bars were a share of the host's capacity, a sliver on hosts with
80 cores. Each figure now sits beside 24 bars of the last 6 hours (1h/24h
per widget), each row scaled to its own peak but never below 1 core or
1 GiB (configurable per widget), so a 0.05-core container reads flat and
a bar's height means real load. One grouped date_bin query feeds them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 15:05:06 -04:00
co-authored by Claude Opus 5.5
parent 97c864bf42
commit 05eaca3f2f
7 changed files with 242 additions and 36 deletions
@@ -1,20 +1,29 @@
{# docker/widget_resources.html — the dashboard Busiest Containers widget
(#5253): a verdict naming the busiest, then a table of the top containers
by CPU (averaged over the last few minutes, so the order holds still). Each
figure carries its unit; the shaded bar behind it is its share of the
host's cores, or of the memory limit (the host's memory when none is set). #}
(#5253, #5214): a verdict naming the busiest, then the top containers by CPU
(averaged over the last few minutes, so the order holds still). Each figure
carries its unit, beside bars of the last few hours. A row's bars are scaled
to its own peak, but never below a floor (1 core, 1 GiB by default), so a
container idling at 0.05 cores reads flat and a spike means real load. #}
{% macro cores(v) -%}{{ "%.2f"|format(v) if v < 1 else "%.1f"|format(v) }}{%- endmacro %}
{% macro bars(b, peak_text, roof_text) -%}
<span class="w-bars w-wide-only" aria-hidden="true"
title="{% if b.peak is none %}No samples in the last {{ hours }}h{% else %}Peak {{ peak_text }} in the last {{ hours }}h; bars reach the top at {{ roof_text }}{% endif %}">
{%- for h in b.bars %}<i{% if h is none %} class="gap"{% else %} style="height:{{ [h, 4]|max }}%"{% endif %}></i>{% endfor -%}
</span>
{%- endmacro %}
{% if not rows %}
<div class="w-verdict" style="color:var(--text-muted);">No running containers.</div>
{% else %}
{% set top = rows[0] %}
<div class="w-verdict">
Busiest: <strong>{{ top.label }}</strong> on {{ top.host_name }},
{% if top.usage.cores is not none %}{{ "%.2f"|format(top.usage.cores) if top.usage.cores < 1 else "%.1f"|format(top.usage.cores) }}
{% if top.usage.cores is not none %}{{ cores(top.usage.cores) }}
{%- if top.usage.cpus %} of {{ top.usage.cpus }} cores{% else %} cores{% endif %}{% else %}no CPU reading yet{% endif %}.
</div>
<table class="w-table">
<colgroup><col><col class="w-wide-only" style="width:7.5rem"><col style="width:5.5rem"><col style="width:6.5rem"></colgroup>
<thead><tr><th>Container</th><th class="w-wide-only w-text">Host</th><th>CPU</th><th>Memory</th></tr></thead>
<colgroup><col><col class="w-wide-only" style="width:7.5rem"><col class="w-barcol"><col class="w-barcol"></colgroup>
<thead><tr><th>Container</th><th class="w-wide-only w-text">Host</th>
<th title="Cores, averaged over the last {{ minutes }} minutes">CPU</th><th title="In use now">Memory</th></tr></thead>
<tbody>
{% for r in rows %}
{% set u = r.usage %}
@@ -23,19 +32,19 @@
{%- if session.user_id %}<a href="/plugins/docker/container/{{ r.c.host_id }}/{{ r.c.name }}">{{ r.label }}</a>{% else %}{{ r.label }}{% endif -%}
</td>
<td class="w-wide-only w-text" title="{{ r.host_name }}">{{ r.host_name }}</td>
{% if u.cores is none %}<td>—</td>{% else %}
<td class="w-meter tone-{{ status_class(u.cpu_share, 'cpu') if u.cpus else 'dim' }}" style="--pct:{{ [[u.cpu_share or 0, 0]|max, 100]|min }}%"
title="{% if u.cpus %}{{ '%.1f'|format(u.cpu_share) }}% of {{ r.host_name }}'s {{ u.cpus }} cores{% else %}The host's core count isn't known{% endif %}">
{{- "%.2f"|format(u.cores) if u.cores < 1 else "%.1f"|format(u.cores) }} cores</td>
{% endif %}
{% if u.mem_bytes is none %}<td>—</td>{% else %}
<td class="w-meter tone-{{ status_class(u.mem_pct, 'mem') if u.mem_of else 'dim' }}" style="--pct:{{ [[u.mem_pct or 0, 0]|max, 100]|min }}%"
title="{% if u.mem_pct is not none and u.mem_of %}{{ '%.1f'|format(u.mem_pct) }}% of {{ u.mem_of }}{% else %}No memory limit set{% endif %}">
{{- u.mem_bytes|bytes }}</td>
{% endif %}
<td class="tone-{{ status_class(u.cpu_share, 'cpu') if u.cpus and u.cores is not none else 'dim' }}"
{% if u.cpus and u.cores is not none %}title="{{ '%.1f'|format(u.cpu_share) }}% of {{ r.host_name }}'s {{ u.cpus }} cores"{% endif %}>
{{- bars(r.cpu_bars, (cores(r.cpu_bars.peak) ~ " cores") if r.cpu_bars.peak is not none else "", cores(r.cpu_bars.roof) ~ " cores") -}}
{%- if u.cores is none %}—{% else %}{{ cores(u.cores) }} cores{% endif -%}
</td>
<td class="tone-{{ status_class(u.mem_pct, 'mem') if u.mem_of and u.mem_bytes is not none else 'dim' }}"
{% if u.mem_pct is not none and u.mem_of and u.mem_bytes is not none %}title="{{ '%.1f'|format(u.mem_pct) }}% of {{ u.mem_of }}"{% endif %}>
{{- bars(r.mem_bars, r.mem_bars.peak|bytes if r.mem_bars.peak is not none else "", r.mem_bars.roof|bytes) -}}
{%- if u.mem_bytes is none %}—{% else %}{{ u.mem_bytes|bytes }}{% endif -%}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="w-note" title="Shading: share of the host's cores, and of the memory limit (or the host's memory when no limit is set).">CPU over the last {{ minutes }} min; shading is the share of the host.</div>
<div class="w-note w-wide-only">Bars: last {{ hours }}h, each row up to its own peak, at least {{ cores(cpu_floor) }} core{{ 's' if cpu_floor != 1 }} and {{ mem_floor|bytes }}.</div>
{% endif %}