diff --git a/distributed/dashboard/components/scheduler.py b/distributed/dashboard/components/scheduler.py index 859c55c57a..31224d5c12 100644 --- a/distributed/dashboard/components/scheduler.py +++ b/distributed/dashboard/components/scheduler.py @@ -4076,6 +4076,7 @@ def __init__(self, scheduler, **kwargs): "host_disk_io.read_bps", "host_disk_io.write_bps", "cpu_fraction", + "_is_total", ] workers = self.scheduler.workers.values() self.extra_names = sorted( @@ -4128,7 +4129,14 @@ def __init__(self, scheduler, **kwargs): } formatters = { - "cpu": NumberFormatter(format="0 %"), + # Use a pure number (0 to nthreads) on the total line and a % + # (e.g. 0 to 400% for 4 threads per worker ) on the individual workers. + # It would be very confusing to read e.g. 9000% on the total, whereas + # seeing that ~90 CPU equivalents are being fully used is more meaningful. + "cpu": HTMLTemplateFormatter( + template="<% if (_is_total) { %><%= (value).toFixed(1) %>" + "<% } else { %><%= Math.round(value * 100) %> %<% } %>" + ), "memory_percent": NumberFormatter(format="0.0 %"), "memory": NumberFormatter(format="0.0 b"), "memory_limit": NumberFormatter(format="0.0 b"), @@ -4281,11 +4289,15 @@ def update(self): data["cpu"][-1] = ws.metrics["cpu"] / 100.0 data["cpu_fraction"][-1] = ws.metrics["cpu"] / 100.0 / ws.nthreads data["nthreads"][-1] = ws.nthreads + data["_is_total"][-1] = False for name in self.names + self.extra_names: if name == "name": data[name].insert(0, f"Total ({len(data[name])})") continue + if name == "_is_total": + data[name].insert(0, True) + continue try: if len(self.scheduler.workers) == 0: total_data = None @@ -4308,7 +4320,6 @@ def update(self): total_data = ( sum(ws.metrics["cpu"] for ws in self.scheduler.workers.values()) / 100 - / len(self.scheduler.workers.values()) ) elif name == "cpu_fraction": total_data = ( diff --git a/distributed/dashboard/tests/test_scheduler_bokeh.py b/distributed/dashboard/tests/test_scheduler_bokeh.py index bf2f843028..2836e4fa75 100644 --- a/distributed/dashboard/tests/test_scheduler_bokeh.py +++ b/distributed/dashboard/tests/test_scheduler_bokeh.py @@ -564,6 +564,17 @@ async def test_WorkerTable(c, s, a, b): assert all(nthreads) assert nthreads[0] == nthreads[1] + nthreads[2] + # Total CPU should show raw core count (sum of all worker CPU / 100) + cpu = wt.source.data["cpu"] + expected_cpu_total = sum(ws.metrics["cpu"] for ws in s.workers.values()) / 100 + assert cpu[0] == expected_cpu_total + + # _is_total flag should be set correctly + is_total = wt.source.data["_is_total"] + assert is_total[0] is True + assert is_total[1] is False + assert is_total[2] is False + @gen_cluster(client=True) async def test_WorkerTable_custom_metrics(c, s, a, b):