Skip to content

Commit e994cfc

Browse files
committed
Make unit selection dropdown & dynamic
1 parent 0ed4cc4 commit e994cfc

File tree

1 file changed

+93
-100
lines changed

1 file changed

+93
-100
lines changed

distributed/dashboard/components/scheduler.py

Lines changed: 93 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@
3737
PanTool,
3838
Range1d,
3939
ResetTool,
40+
Select,
4041
Tabs,
4142
TapTool,
4243
Title,
43-
Toggle,
4444
VeeHead,
4545
WheelZoomTool,
4646
)
@@ -3396,58 +3396,64 @@ def __init__(self, scheduler, **kwargs):
33963396
self.init_root()
33973397

33983398
def init_root(self):
3399-
def handle_toggle(_toggle):
3400-
self.toggle.label = (
3401-
"Bytes (Toggle for Values)"
3402-
if self.toggle.active
3403-
else "Timing (Toggle for Bytes)"
3404-
)
3399+
def handle_selector_chng(attr, old, new):
3400+
self.freq_selected = new
34053401
self.substantial_change = True
34063402

34073403
self.function_selector = MultiChoice(value=[], options=[])
34083404
self.function_selector.placeholder = "Select specific functions"
3409-
self.toggle = Toggle(label="Timing (Toggle for Bytes)")
3410-
self.toggle.on_click(handle_toggle)
3405+
self.freq_selector = Select(title="Freq selection", options=[])
3406+
self.freq_selector.on_change("value", handle_selector_chng)
3407+
self.freq_selected = "seconds"
34113408
self.task_exec_by_activity_chart = figure()
34123409
self.task_exec_by_prefix_chart = figure()
34133410
self.senddata_by_activity_chart = figure()
34143411
self.root = column(
34153412
self.function_selector,
3416-
self.toggle,
3413+
self.freq_selector,
34173414
row([self.task_exec_by_prefix_chart, self.task_exec_by_activity_chart]),
34183415
row([self.senddata_by_activity_chart]),
34193416
sizing_mode="scale_width",
34203417
)
34213418

3419+
def format(self, freq: str, val: Any) -> str:
3420+
formatters = {"bytes": format_bytes, "seconds": format_time}
3421+
return formatters.get(freq, str)(val)
3422+
34223423
@without_property_validation
34233424
@log_errors
34243425
def update(self):
34253426
items = sorted(
34263427
(k, v)
34273428
for k, v in self.scheduler.cumulative_worker_metrics.items()
3428-
if isinstance(k, tuple) and k[-1] in ("bytes", "seconds")
3429+
if isinstance(k, tuple)
34293430
)
34303431
for (type, *parts), val in items:
34313432
if type == "get-data":
34323433
operation, freq = parts
3434+
3435+
if freq not in self.freq_selector.options:
3436+
# note append doesn't work here
3437+
self.freq_selector.options += [freq]
3438+
34333439
if operation not in self.senddata["operation"]:
34343440
self.substantial_change = True
34353441
self.senddata["operation"].append(operation)
34363442

34373443
idx = self.senddata["operation"].index(operation)
3438-
while idx >= len(self.senddata[f"{operation}_value"]):
3439-
self.senddata[f"{operation}_bytes"].append(0)
3440-
self.senddata[f"{operation}_value"].append(0)
3441-
self.senddata[f"{operation}_text"].append(0)
3442-
if freq == "bytes":
3443-
self.senddata[f"{operation}_text"][idx] = format_bytes(val)
3444-
self.senddata[f"{operation}_bytes"][idx] = val
3445-
elif freq == "seconds":
3446-
self.senddata[f"{operation}_text"][idx] = format_time(val)
3447-
self.senddata[f"{operation}_value"][idx] = val
3444+
while idx >= len(self.senddata[f"{operation}_{freq}"]):
3445+
self.senddata[f"{operation}_{freq}"].append(0)
3446+
self.senddata[f"{operation}_{freq}_text"].append("")
3447+
self.senddata[f"{operation}_{freq}_text"][idx] = self.format(freq, val)
3448+
self.senddata[f"{operation}_{freq}"][idx] = val
34483449

34493450
elif type == "execute":
34503451
function_name, operation, freq = parts
3452+
3453+
if freq not in self.freq_selector.options:
3454+
# note append doesn't work here
3455+
self.freq_selector.options += [freq]
3456+
34513457
if operation not in self.task_operations:
34523458
self.substantial_change = True
34533459
self.task_operations.append(operation)
@@ -3461,19 +3467,16 @@ def update(self):
34613467

34623468
# Some function/operation combos missing, so need to keep columns aligned
34633469
for op in self.task_operations:
3464-
while len(self.task_exec_data[f"{op}_value"]) != len(
3470+
while len(self.task_exec_data[f"{op}_{freq}"]) != len(
34653471
self.task_exec_data["functions"]
34663472
):
3467-
self.task_exec_data[f"{op}_value"].append(0)
3468-
self.task_exec_data[f"{op}_bytes"].append(0)
3469-
self.task_exec_data[f"{op}_text"].append("")
3473+
self.task_exec_data[f"{op}_{freq}"].append(0)
3474+
self.task_exec_data[f"{op}_{freq}_text"].append("")
34703475

3471-
if freq == "seconds":
3472-
self.task_exec_data[f"{operation}_text"][idx] = format_time(val)
3473-
self.task_exec_data[f"{operation}_value"][idx] = val
3474-
elif freq == "bytes":
3475-
self.task_exec_data[f"{operation}_text"][idx] = format_bytes(val)
3476-
self.task_exec_data[f"{operation}_bytes"][idx] = val
3476+
self.task_exec_data[f"{operation}_{freq}"][idx] = val
3477+
self.task_exec_data[f"{operation}_{freq}_text"][idx] = self.format(
3478+
freq, val
3479+
)
34773480

34783481
data = self.task_exec_data.copy()
34793482
# If user has manually selected function(s) then we are only showing them.
@@ -3495,9 +3498,13 @@ def update(self):
34953498
f"Filter by function ({len(self.function_selector.options)}):"
34963499
)
34973500

3498-
task_exec_piechart = self._build_task_execution_by_activity_chart()
3499-
task_exec_barchart = self._build_task_execution_by_prefix_chart()
3500-
senddata_piechart = self._build_senddata_chart()
3501+
task_exec_piechart = self._build_task_execution_by_activity_chart(
3502+
self.task_exec_data_limited.copy()
3503+
)
3504+
task_exec_barchart = self._build_task_execution_by_prefix_chart(
3505+
self.task_exec_data_limited.copy()
3506+
)
3507+
senddata_piechart = self._build_senddata_chart(self.senddata.copy())
35013508

35023509
# replacing the child causes small blips if done every iteration vs updating renderers
35033510
# but it's needed when new functions and/or operations show up to rerender plot
@@ -3511,28 +3518,20 @@ def update(self):
35113518
self.task_exec_by_activity_chart.renderers = task_exec_barchart.renderers
35123519
self.senddata_by_activity_chart.renderers = senddata_piechart.renderers
35133520

3514-
def _build_task_execution_by_activity_chart(self):
3515-
show_bytes = self.toggle.active
3521+
def _build_task_execution_by_activity_chart(
3522+
self, task_exec_data: defaultdict[str, list]
3523+
) -> figure:
35163524
piechart_data = dict()
35173525
piechart_data["value"] = [
3518-
sum(
3519-
self.task_exec_data_limited[
3520-
f"{op}_{'bytes' if show_bytes else 'value'}"
3521-
]
3522-
)
3526+
sum(task_exec_data[f"{op}_{self.freq_selected}"])
35233527
for op in self.task_operations
35243528
]
35253529
piechart_data["text"] = [
3526-
format_bytes(n) if show_bytes else format_time(n)
3527-
for n in piechart_data["value"]
3530+
self.format(self.freq_selected, v) for v in piechart_data["value"]
35283531
]
35293532
piechart_data["angle"] = [
35303533
(
3531-
sum(
3532-
self.task_exec_data_limited[
3533-
f"{operation}_{'bytes' if show_bytes else 'value'}"
3534-
]
3535-
)
3534+
sum(task_exec_data[f"{operation}_{self.freq_selected}"])
35363535
/ sum(piechart_data["value"])
35373536
if sum(piechart_data["value"])
35383537
else 0 # may not have any bytes movement reported, avoid divide by zero
@@ -3571,9 +3570,11 @@ def _build_task_execution_by_activity_chart(self):
35713570
)
35723571
return piechart
35733572

3574-
def _build_task_execution_by_prefix_chart(self):
3573+
def _build_task_execution_by_prefix_chart(
3574+
self, task_exec_data: defaultdict[str, list]
3575+
) -> figure:
35753576
barchart = figure(
3576-
x_range=self.task_exec_data_limited["functions"],
3577+
x_range=task_exec_data["functions"],
35773578
height=500,
35783579
title="Task execution, by prefix",
35793580
tools="pan,wheel_zoom,box_zoom,reset",
@@ -3582,67 +3583,59 @@ def _build_task_execution_by_prefix_chart(self):
35823583
barchart.yaxis.visible = False
35833584
barchart.xaxis.major_label_orientation = 0.2
35843585
barchart.grid.grid_line_color = None
3585-
renderers = barchart.vbar_stack(
3586-
[
3587-
name
3588-
for name in self.task_exec_data_limited.keys()
3589-
if name.endswith("bytes" if self.toggle.active else "value")
3590-
and len(self.task_exec_data_limited[name])
3591-
],
3592-
x="functions",
3593-
width=0.9,
3594-
source=self.task_exec_by_prefix_src,
3595-
color=small_palettes["YlGnBu"].get(len(self.task_operations), []),
3596-
legend_label=self.task_operations,
3597-
)
3598-
for vbar in renderers:
3599-
tooltips = [
3600-
(
3601-
vbar.name,
3602-
f"@{{{vbar.name.replace('_bytes', '').replace('_value', '')}_text}}",
3603-
),
3604-
("function", "@functions"),
3605-
]
3606-
barchart.add_tools(HoverTool(tooltips=tooltips, renderers=[vbar]))
3586+
stackers = [
3587+
name for name in task_exec_data if name.endswith(self.freq_selected)
3588+
]
3589+
if stackers:
3590+
renderers = barchart.vbar_stack(
3591+
stackers,
3592+
x="functions",
3593+
width=0.9,
3594+
source=self.task_exec_by_prefix_src,
3595+
color=small_palettes["YlGnBu"].get(len(self.task_operations), []),
3596+
legend_label=self.task_operations,
3597+
)
3598+
for vbar in renderers:
3599+
tooltips = [
3600+
(
3601+
vbar.name,
3602+
f"@{{{vbar.name}_text}}",
3603+
),
3604+
("function", "@functions"),
3605+
]
3606+
barchart.add_tools(HoverTool(tooltips=tooltips, renderers=[vbar]))
36073607

3608-
if any(
3609-
len(self.task_exec_by_prefix_src.data[k])
3610-
!= len(self.task_exec_data_limited[k])
3611-
for k in self.task_exec_by_prefix_src.data
3612-
):
3613-
self.substantial_change = True
3614-
self.task_exec_by_prefix_src.data = dict(self.task_exec_data_limited)
3615-
barchart.renderers = renderers
3616-
return barchart
3608+
if any(
3609+
len(self.task_exec_by_prefix_src.data[k]) != len(task_exec_data[k])
3610+
for k in self.task_exec_by_prefix_src.data
3611+
):
3612+
self.substantial_change = True
36173613

3618-
def _build_senddata_chart(self):
3619-
show_bytes = self.toggle.active
3614+
self.task_exec_by_prefix_src.data = dict(task_exec_data)
3615+
barchart.renderers = renderers
3616+
return barchart
36203617

3621-
senddata = dict()
3622-
senddata["operation"] = self.senddata["operation"]
3623-
senddata["value"] = [
3624-
(sum(self.senddata[f"{op}_{'bytes' if show_bytes else 'value'}"]))
3625-
for op in self.senddata["operation"]
3626-
]
3627-
senddata["text"] = [
3628-
format_bytes(n) if show_bytes else format_time(n) for n in senddata["value"]
3618+
def _build_senddata_chart(self, senddata: defaultdict[str, list]) -> figure:
3619+
piedata = dict()
3620+
piedata["operation"] = senddata["operation"]
3621+
piedata["value"] = [
3622+
(sum(senddata[f"{op}_{self.freq_selected}"]))
3623+
for op in senddata["operation"]
36293624
]
3630-
senddata["angle"] = [
3625+
piedata["text"] = [self.format(self.freq_selected, v) for v in piedata["value"]]
3626+
piedata["angle"] = [
36313627
(
3632-
(
3633-
sum(self.senddata[f"{op}_{'bytes' if show_bytes else 'value'}"])
3634-
/ sum(senddata["value"])
3635-
)
3636-
if sum(senddata["value"])
3628+
(sum(senddata[f"{op}_{self.freq_selected}"]) / sum(piedata["value"]))
3629+
if sum(piedata["value"])
36373630
else 0.0
36383631
)
36393632
* 2
36403633
* math.pi
3641-
for op in senddata["operation"]
3634+
for op in piedata["operation"]
36423635
]
3643-
senddata["color"] = small_palettes["YlGnBu"].get(len(senddata["operation"]), [])
3636+
piedata["color"] = small_palettes["YlGnBu"].get(len(piedata["operation"]), [])
36443637

3645-
self.sendsrc.data = senddata
3638+
self.sendsrc.data = piedata
36463639
senddata_piechart = figure(
36473640
height=500,
36483641
title="Send data, by activity",

0 commit comments

Comments
 (0)