Repository: dask/distributed

Issue:
Add Network send/recv to web UI plot
It would be nice to see network activity over time.

We already capture this data in the same way we capture the CPU/Memory data that we already plot, so the job of figuring out how to move the data into the `ColumnDataSource` isn't hard.  What _is_ tricky is that we don't have a percentage, but instead have actual MB/s bandwidth counts.  We would have to add a second Y-axis (this is doable) and think a bit about how best to set the axes.

This might be an interesting task for anyone looking to get into the Bokeh Web UI, notably @martindurant 



Discussion:
Typical is to plot the percentage from peak (on the same axis scale), but include a label/legend saying what the peak was. 
A second axes with transfer bandwidth, auto-scaled (to sensible boundaries), is totally doable too.


Resolving patch:
diff --git a/distributed/bokeh/status/server_lifecycle.py b/distributed/bokeh/status/server_lifecycle.py
--- a/distributed/bokeh/status/server_lifecycle.py
+++ b/distributed/bokeh/status/server_lifecycle.py
@@ -83,7 +83,8 @@ def on_server_loaded(server_context):
                            'index': deque(maxlen=n),
                            'plot-data': {'time': deque(maxlen=n),
                                          'cpu': deque(maxlen=n),
-                                         'memory-percent': deque(maxlen=n)}}
+                                         'memory-percent': deque(maxlen=n),
+                                         'network-send': deque(maxlen=n)}}
     server_context.add_periodic_callback(workers, 500)
 
     messages['tasks'] = {'interval': 100,
diff --git a/distributed/bokeh/worker_monitor.py b/distributed/bokeh/worker_monitor.py
--- a/distributed/bokeh/worker_monitor.py
+++ b/distributed/bokeh/worker_monitor.py
@@ -9,13 +9,13 @@
 
 with ignoring(ImportError):
     from bokeh.models import (ColumnDataSource, DataRange1d, Range1d,
-            NumeralTickFormatter)
+            NumeralTickFormatter, LinearAxis)
     from bokeh.palettes import Spectral9
     from bokeh.plotting import figure
 
 
 def resource_profile_plot(width=600, height=300):
-    names = ['time', 'cpu', 'memory-percent']
+    names = ['time', 'cpu', 'memory-percent', 'network-send']
     source = ColumnDataSource({k: [] for k in names})
 
     x_range = DataRange1d(follow='end', follow_interval=30000, range_padding=0)
@@ -30,6 +30,14 @@ def resource_profile_plot(width=600, height=300):
     p.legend[0].location = 'top_left'
     p.yaxis[0].formatter = NumeralTickFormatter(format="0 %")
     p.min_border_right = 10
+    p.extra_y_ranges = {"send": DataRange1d(bounds=(0, None))}
+    p.add_layout(LinearAxis(y_range_name="send", axis_label="Throughput (MB/s)"),
+                 'right')
+    p.yaxis.axis_label_text_font_size = "10pt"
+
+    p.line(x='time', y='network-send', line_width=2, line_alpha=0.8,
+           color=Spectral9[2], legend='Network', source=source,
+           y_range_name="send")
 
     return source, p
 
@@ -39,7 +47,7 @@ def resource_profile_update(source, worker_buffer, times_buffer):
 
     workers = sorted(list(set(chain(*list(w.keys() for w in worker_buffer)))))
 
-    for name in ['cpu', 'memory-percent']:
+    for name in ['cpu', 'memory-percent', 'network-send']:
         data[name] = [[msg[w][name] if w in msg and name in msg[w] else 'null'
                        for msg in worker_buffer]
                        for w in workers]
@@ -60,6 +68,13 @@ def resource_append(lists, msg):
         lists[k].append(mean(pluck(k, L)) / 100)
 
     lists['time'].append(mean(pluck('time', L)) * 1000)
+    net = mean(pluck('network-send', L, 0))
+    if len(lists['time']) >= 2:
+        t1, t2 = lists['time'][-2], lists['time'][-1]
+        interval = (t2 - t1) / 1000
+    else:
+        interval = 0.5
+    lists['network-send'].append(net / 2**20 / interval)
 
 
 def mean(seq):


Test patch:
diff --git a/distributed/bokeh/tests/test_worker_monitor.py b/distributed/bokeh/tests/test_worker_monitor.py
--- a/distributed/bokeh/tests/test_worker_monitor.py
+++ b/distributed/bokeh/tests/test_worker_monitor.py
@@ -21,8 +21,10 @@ def test_resource_monitor_plot(s, a, b):
     times_buffer = [1000, 1001, 1003]
     workers_buffer = [{},
                       {'10.10.20.86': {'cpu': 15.9, 'memory-percent': 63.0}},
-                      {'10.10.20.86': {'cpu': 14.9, 'memory-percent': 64.0},
-                       '10.10.20.87': {'cpu': 13.9, 'memory-percent': 64.0}}]
+                      {'10.10.20.86': {'cpu': 14.9, 'memory-percent': 64.0,
+                                       'network-send': 2**16},
+                       '10.10.20.87': {'cpu': 13.9, 'memory-percent': 64.0,
+                                       'network-send': 2**17}}]
 
     source, plot = resource_profile_plot()
     resource_profile_update(source, workers_buffer, times_buffer)
@@ -37,11 +39,17 @@ def test_resource_monitor_plot(s, a, b):
     assert len(source.data['times']) == 2
     assert len(source.data['memory-percent']) == 2
 
+    assert len(source.data['network-send']) == 2
+    assert source.data['network-send'][1] == ['null', 'null', 2**17]
+
 
 def test_resource_append():
-    lists = {'time': [], 'cpu': [], 'memory-percent': []}
-    msg = {'10.10.20.86': {'cpu': 10, 'memory-percent': 50, 'time': 2000},
-           '10.10.20.87': {'cpu': 30, 'memory-percent': 70, 'time': 1000}}
+    lists = {'time': [], 'cpu': [], 'memory-percent': [], 'network-send': []}
+    msg = {'10.10.20.86': {'cpu': 10, 'memory-percent': 50, 'time': 2000,
+                           'network-send': 2**16},
+           '10.10.20.87': {'cpu': 30, 'memory-percent': 70, 'time': 1000,
+                           'network-send': 2**17}}
 
     resource_append(lists, msg)
-    assert lists == {'time': [1500000], 'cpu': [0.2], 'memory-percent': [0.6]}
+    assert lists == {'time': [1500000], 'cpu': [0.2], 'memory-percent': [0.6],
+                     'network-send': [0.1875]}