Repository: dask/distributed

Issue:
bokeh "counters" tab is different from others
It seems that one of the tabs in the new bokeh status layout has an ill-defined tab. The pages defined [here](https://github.com/dask/distributed/blob/040011a5196972f61d9a7c7ad26ec81bc6f066f5/distributed/bokeh/worker.py#L35) differ from others. They also refer to pages that do not exist.


Discussion:
Good catch.  We share the `Counters` page between both the worker and scheduler.  It looks like we currently import the function that makes the document when instead we should import the class and recreate the `counters_doc` function in scheduler.py with the correct `template`.

Resolving patch:
diff --git a/distributed/bokeh/scheduler.py b/distributed/bokeh/scheduler.py
--- a/distributed/bokeh/scheduler.py
+++ b/distributed/bokeh/scheduler.py
@@ -876,7 +876,7 @@ def update(self):
         self.source.data.update(data)
 
 
-def systemmonitor_doc(scheduler, doc):
+def systemmonitor_doc(scheduler, extra, doc):
     with log_errors():
         table = StateTable(scheduler)
         sysmon = SystemMonitor(scheduler, sizing_mode='scale_width')
@@ -887,11 +887,11 @@ def systemmonitor_doc(scheduler, doc):
         doc.add_root(column(table.root, sysmon.root,
                             sizing_mode='scale_width'))
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'system'
+        doc.template_variables.update(extra)
 
 
-def stealing_doc(scheduler, doc):
+def stealing_doc(scheduler, extra, doc):
     with log_errors():
         table = StateTable(scheduler)
         occupancy = Occupancy(scheduler, height=200, sizing_mode='scale_width')
@@ -909,11 +909,11 @@ def stealing_doc(scheduler, doc):
                             sizing_mode='scale_width'))
 
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'stealing'
+        doc.template_variables.update(extra)
 
 
-def events_doc(scheduler, doc):
+def events_doc(scheduler, extra, doc):
     with log_errors():
         events = Events(scheduler, 'all', height=250)
         events.update()
@@ -921,11 +921,11 @@ def events_doc(scheduler, doc):
         doc.title = "Dask Scheduler Events"
         doc.add_root(column(events.root, sizing_mode='scale_width'))
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'events'
+        doc.template_variables.update(extra)
 
 
-def workers_doc(scheduler, doc):
+def workers_doc(scheduler, extra, doc):
     with log_errors():
         table = WorkerTable(scheduler)
         table.update()
@@ -933,11 +933,11 @@ def workers_doc(scheduler, doc):
         doc.title = "Dask Workers"
         doc.add_root(table.root)
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'workers'
+        doc.template_variables.update(extra)
 
 
-def tasks_doc(scheduler, doc):
+def tasks_doc(scheduler, extra, doc):
     with log_errors():
         ts = TaskStream(scheduler, n_rectangles=100000, clear_interval=60000,
                         sizing_mode='stretch_both')
@@ -946,11 +946,11 @@ def tasks_doc(scheduler, doc):
         doc.title = "Dask Task Stream"
         doc.add_root(ts.root)
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'tasks'
+        doc.template_variables.update(extra)
 
 
-def status_doc(scheduler, doc):
+def status_doc(scheduler, extra, doc):
     with log_errors():
         task_stream = TaskStream(scheduler, n_rectangles=1000, clear_interval=10000, height=350)
         task_stream.update()
@@ -982,23 +982,30 @@ def status_doc(scheduler, doc):
                             task_progress.root,
                             sizing_mode='scale_width'))
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'status'
+        doc.template_variables.update(extra)
 
 
 class BokehScheduler(BokehServer):
-    def __init__(self, scheduler, io_loop=None, **kwargs):
+    def __init__(self, scheduler, io_loop=None, prefix='', **kwargs):
         self.scheduler = scheduler
         self.server_kwargs = kwargs
-
-        systemmonitor = Application(FunctionHandler(partial(systemmonitor_doc,
-                                                            scheduler)))
-        workers = Application(FunctionHandler(partial(workers_doc, scheduler)))
-        stealing = Application(FunctionHandler(partial(stealing_doc, scheduler)))
-        counters = Application(FunctionHandler(partial(counters_doc, scheduler)))
-        events = Application(FunctionHandler(partial(events_doc, scheduler)))
-        tasks = Application(FunctionHandler(partial(tasks_doc, scheduler)))
-        status = Application(FunctionHandler(partial(status_doc, scheduler)))
+        self.server_kwargs['prefix'] = prefix or None
+        prefix = prefix or ''
+        prefix = prefix.rstrip('/')
+        if prefix and not prefix.startswith('/'):
+            prefix = '/' + prefix
+
+        extra = {'prefix': prefix}
+        extra.update(template_variables)
+
+        systemmonitor = Application(FunctionHandler(partial(systemmonitor_doc, scheduler, extra)))
+        workers = Application(FunctionHandler(partial(workers_doc, scheduler, extra)))
+        stealing = Application(FunctionHandler(partial(stealing_doc, scheduler, extra)))
+        counters = Application(FunctionHandler(partial(counters_doc, scheduler, extra)))
+        events = Application(FunctionHandler(partial(events_doc, scheduler, extra)))
+        tasks = Application(FunctionHandler(partial(tasks_doc, scheduler, extra)))
+        status = Application(FunctionHandler(partial(status_doc, scheduler, extra)))
 
         self.apps = {
                 '/system': systemmonitor,
diff --git a/distributed/bokeh/worker.py b/distributed/bokeh/worker.py
--- a/distributed/bokeh/worker.py
+++ b/distributed/bokeh/worker.py
@@ -535,7 +535,7 @@ def update(self):
 from bokeh.application import Application
 
 
-def main_doc(worker, doc):
+def main_doc(worker, extra, doc):
     with log_errors():
         statetable = StateTable(worker)
         executing_ts = ExecutingTimeSeries(worker, sizing_mode='scale_width')
@@ -559,11 +559,11 @@ def main_doc(worker, doc):
                             communicating_stream.root,
                             sizing_mode='scale_width'))
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'main'
+        doc.template_variables.update(extra)
 
 
-def crossfilter_doc(worker, doc):
+def crossfilter_doc(worker, extra, doc):
     with log_errors():
         statetable = StateTable(worker)
         crossfilter = CrossFilter(worker)
@@ -574,11 +574,11 @@ def crossfilter_doc(worker, doc):
 
         doc.add_root(column(statetable.root, crossfilter.root))
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'crossfilter'
+        doc.template_variables.update(extra)
 
 
-def systemmonitor_doc(worker, doc):
+def systemmonitor_doc(worker, extra, doc):
     with log_errors():
         sysmon = SystemMonitor(worker, sizing_mode='scale_width')
         doc.title = "Dask Worker Monitor"
@@ -586,11 +586,11 @@ def systemmonitor_doc(worker, doc):
 
         doc.add_root(sysmon.root)
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'system'
+        doc.template_variables.update(extra)
 
 
-def counters_doc(server, doc):
+def counters_doc(server, extra, doc):
     with log_errors():
         doc.title = "Dask Worker Counters"
         counter = Counters(server, sizing_mode='stretch_both')
@@ -598,18 +598,28 @@ def counters_doc(server, doc):
 
         doc.add_root(counter.root)
         doc.template = template
-        doc.template_variables.update(template_variables)
         doc.template_variables['active_page'] = 'counters'
+        doc.template_variables.update(extra)
 
 
 class BokehWorker(BokehServer):
-    def __init__(self, worker, io_loop=None, **kwargs):
+    def __init__(self, worker, io_loop=None, prefix='', **kwargs):
         self.worker = worker
         self.server_kwargs = kwargs
-        main = Application(FunctionHandler(partial(main_doc, worker)))
-        crossfilter = Application(FunctionHandler(partial(crossfilter_doc, worker)))
-        systemmonitor = Application(FunctionHandler(partial(systemmonitor_doc, worker)))
-        counters = Application(FunctionHandler(partial(counters_doc, worker)))
+        self.server_kwargs['prefix'] = prefix or None
+        prefix = prefix or ''
+        prefix = prefix.rstrip('/')
+        if prefix and not prefix.startswith('/'):
+            prefix = '/' + prefix
+
+        extra = {'prefix': prefix}
+
+        extra.update(template_variables)
+
+        main = Application(FunctionHandler(partial(main_doc, worker, extra)))
+        crossfilter = Application(FunctionHandler(partial(crossfilter_doc, worker, extra)))
+        systemmonitor = Application(FunctionHandler(partial(systemmonitor_doc, worker, extra)))
+        counters = Application(FunctionHandler(partial(counters_doc, worker, extra)))
 
         self.apps = {'/main': main,
                      '/counters': counters,


Test patch: