feat(docker): Busiest Containers shows bars of the last hours beside each figure, scaled to the row's peak with a floor (#5214)
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:
@@ -218,23 +218,52 @@ def test_a_container_reads_by_its_service_or_ci_job():
|
||||
assert svc.display_name(_c("portainer_agent")) == "portainer_agent"
|
||||
|
||||
|
||||
async def test_busiest_containers_show_the_number_with_its_scale(app):
|
||||
"""#5253: every bar has its figure; the widget names the busiest."""
|
||||
def _bars(values, floor):
|
||||
return svc.scaled_bars(values, floor)
|
||||
|
||||
|
||||
async def test_busiest_containers_show_the_number_beside_its_recent_bars(app):
|
||||
"""#5253, #5214: every figure has its unit; the bars beside it are the last
|
||||
hours, scaled to the row's own peak; the widget names the busiest."""
|
||||
rows = [
|
||||
{"c": _c("GITEA-ACTIONS-TASK-1-WORKFLOW-Android-JOB-build-0123456789abcdef0"),
|
||||
"label": "CI: Android · build", "host_name": "VDNT-Docker03",
|
||||
"usage": {"cores": 1.62, "cpus": 8, "cpu_share": 20.25, "mem_bytes": 820 * 2**20,
|
||||
"mem_pct": 5.0, "mem_of": "the host's memory", "mem_limit": None}},
|
||||
"mem_pct": 5.0, "mem_of": "the host's memory", "mem_limit": None},
|
||||
"cpu_bars": _bars([None, 0.8, 3.2, 1.6], 1.0), "mem_bars": _bars([None] * 4, 2**30)},
|
||||
{"c": _c("tdarr"), "label": "tdarr", "host_name": "VDNT-Docker03",
|
||||
"usage": {"cores": 0.25, "cpus": 8, "cpu_share": 3.1, "mem_bytes": None,
|
||||
"mem_pct": None, "mem_of": None, "mem_limit": None}},
|
||||
"mem_pct": None, "mem_of": None, "mem_limit": None},
|
||||
"cpu_bars": _bars([0.25] * 4, 1.0), "mem_bars": _bars([None] * 4, 2**30)},
|
||||
]
|
||||
async with app.test_request_context("/plugins/docker/widget/resources"):
|
||||
page = await render_plugin_template(app, "docker", "docker/widget_resources.html",
|
||||
rows=rows, minutes=5, widget_id="1")
|
||||
rows=rows, minutes=5, hours=6, cpu_floor=1.0,
|
||||
mem_floor=2**30, widget_id="1")
|
||||
assert "Busiest: <strong>CI: Android · build</strong> on VDNT-Docker03" in page and "1.6 of 8 cores" in page
|
||||
assert ">1.6 cores<" in page and ">0.25 cores<" in page and ">820.0 MiB<" in page
|
||||
assert "--pct:20.25%" in page and ">—<" in page
|
||||
assert ">1.6 cores<" in page and ">0.25 cores<" in page and ">820.0 MiB<" in page and ">—<" in page
|
||||
# Android peaks at 3.2 cores, so 1.6 is half height; tdarr's 0.25 sits under the 1-core floor.
|
||||
assert 'class="gap"' in page and "height:50.0%" in page and "height:25.0%" in page
|
||||
assert "Peak 3.2 cores in the last 6h; bars reach the top at 3.2 cores" in page
|
||||
assert "Peak 0.25 cores in the last 6h; bars reach the top at 1.0 cores" in page
|
||||
assert "No samples in the last 6h" in page
|
||||
assert len(re.findall(r"<th[ >]", page)) == 4
|
||||
assert ">CI: Android · build<" in page # the long CI name only as the hover title
|
||||
assert not hex_colours(page), hex_colours(page)
|
||||
|
||||
|
||||
def test_bars_reach_the_peak_but_never_below_the_floor():
|
||||
"""#5214: a 0.05 → 0.10 wobble stays near the floor; a 1.5 → 3 spike fills."""
|
||||
idle = svc.scaled_bars([0.05, 0.10, None], 1.0)
|
||||
assert idle == {"bars": [5.0, 10.0, None], "peak": 0.10, "roof": 1.0}
|
||||
busy = svc.scaled_bars([1.5, 3.0], 1.0)
|
||||
assert busy["bars"] == [50.0, 100.0] and busy["roof"] == 3.0
|
||||
assert svc.scaled_bars([None, None], 1.0) == {"bars": [None, None], "peak": None, "roof": 1.0}
|
||||
|
||||
|
||||
def test_bar_slots_end_at_the_bucket_now_falls_in():
|
||||
now = datetime(2026, 10, 7, 12, 7, 30, tzinfo=timezone.utc)
|
||||
slots, width = svc.bar_slots(now, 6)
|
||||
assert width == 900 and len(slots) == 24
|
||||
assert slots[-1] == datetime(2026, 10, 7, 12, 0, tzinfo=timezone.utc)
|
||||
assert slots[0] == datetime(2026, 10, 7, 6, 15, tzinfo=timezone.utc)
|
||||
|
||||
Reference in New Issue
Block a user