Repository: dask/dask

Issue:
Diagnostics are not compatible with new versions of Bokeh
<pre class="prettyprint lang-py"><code>import pandas as pd
import numpy as np
from dask.diagnostics import Profiler, ResourceProfiler, visualize
from dask import delayed, compute
@delayed
def return_df():
    return pd.DataFrame(np.random.randn(50, 4), columns=list('ABCD'))
df = return_df()
with Profiler() as prof, ResourceProfiler(dt=0.1) as rprof:
    _ = compute(df)
visualize([prof, rprof])
</code></pre>

<img width="185" alt="badbokeh" src="https://user-images.githubusercontent.com/1240693/31522436-a3bf0740-af62-11e7-9768-826b06e111f4.PNG">

This is with bokeh versions: `0.12.7` and `0.12.6`
dask version: `0.15.2`

But when I downgrade bokeh to `0.12.3` (which I guessed by searching `bokeh` in the dask repository :)), the diagnostic visualizations are working fine.



Discussion:
Have you tried more recent Bokeh versions?  Does this error still persist?

On Thu, Oct 12, 2017 at 6:34 PM, eknath <notifications@github.com> wrote:

> import pandas as pd
> import numpy as np
> from dask.diagnostics import Profiler, ResourceProfiler, visualize
> from dask import delayed, compute
> @delayed
> def return_df():
>     return pd.DataFrame(np.random.randn(50, 4), columns=list('ABCD'))
> df = return_df()
> with Profiler() as prof, ResourceProfiler(dt=0.1) as rprof:
>     _ = compute(df)
> visualize([prof, rprof])
>
> [image: badbokeh]
> <https://user-images.githubusercontent.com/1240693/31522436-a3bf0740-af62-11e7-9768-826b06e111f4.PNG>
>
> This is with bokeh versions: 0.12.7 and 0.12.6
> dask version: 0.15.2
>
> But when I downgrade bokeh to 0.12.3 (which I guessed by searching bokeh
> in the dask repository :)), the diagnostic visualizations are working fine.
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/dask/dask/issues/2770>, or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AASszKmTLI1Xc8TcYM1lD2PhCLG2e1K1ks5srpQQgaJpZM4P3uXu>
> .
>


Resolving patch:
diff --git a/dask/diagnostics/profile_visualize.py b/dask/diagnostics/profile_visualize.py
--- a/dask/diagnostics/profile_visualize.py
+++ b/dask/diagnostics/profile_visualize.py
@@ -237,6 +237,7 @@ def plot_tasks(results, dsk, palette='Viridis', label_size=60, **kwargs):
 
     defaults = dict(title="Profile Results",
                     tools="hover,save,reset,xwheel_zoom,xpan",
+                    toolbar_location='above',
                     plot_width=800, plot_height=300)
     defaults.update((k, v) for (k, v) in kwargs.items() if k in
                     _get_figure_keywords())
@@ -317,6 +318,7 @@ def plot_resources(results, palette='Viridis', **kwargs):
 
     defaults = dict(title="Profile Results",
                     tools="save,reset,xwheel_zoom,xpan",
+                    toolbar_location='above',
                     plot_width=800, plot_height=300)
     defaults.update((k, v) for (k, v) in kwargs.items() if k in
                     _get_figure_keywords())
@@ -324,15 +326,18 @@ def plot_resources(results, palette='Viridis', **kwargs):
         t, mem, cpu = zip(*results)
         left, right = min(t), max(t)
         t = [i - left for i in t]
-        p = bp.figure(y_range=(0, max(cpu)), x_range=(0, right - left), **defaults)
+        p = bp.figure(y_range=fix_bounds(0, max(cpu), 100),
+                      x_range=fix_bounds(0, right - left, 1),
+                      **defaults)
     else:
         t = mem = cpu = []
-        p = bp.figure(y_range=(0, 100), x_range=(0, 10), **defaults)
+        p = bp.figure(y_range=(0, 100), x_range=(0, 1), **defaults)
     colors = palettes.all_palettes[palette][6]
     p.line(t, cpu, color=colors[0], line_width=4, legend='% CPU')
     p.yaxis.axis_label = "% CPU"
-    p.extra_y_ranges = {'memory': Range1d(start=(min(mem) if mem else 0),
-                                          end=(max(mem) if mem else 100))}
+    p.extra_y_ranges = {'memory': Range1d(*fix_bounds(min(mem) if mem else 0,
+                                                      max(mem) if mem else 100,
+                                                      100))}
     p.line(t, mem, color=colors[2], y_range_name='memory', line_width=4,
            legend='Memory')
     p.add_layout(LinearAxis(y_range_name='memory', axis_label='Memory (MB)'),
@@ -341,6 +346,11 @@ def plot_resources(results, palette='Viridis', **kwargs):
     return p
 
 
+def fix_bounds(start, end, min_span):
+    """Adjust end point to ensure span of at least `min_span`"""
+    return start, max(end, start + min_span)
+
+
 def plot_cache(results, dsk, start_time, metric_name, palette='Viridis',
                label_size=60, **kwargs):
     """Visualize the results of profiling in a bokeh plot.
@@ -374,6 +384,7 @@ def plot_cache(results, dsk, start_time, metric_name, palette='Viridis',
 
     defaults = dict(title="Profile Results",
                     tools="hover,save,reset,wheel_zoom,xpan",
+                    toolbar_location='above',
                     plot_width=800, plot_height=300)
     defaults.update((k, v) for (k, v) in kwargs.items() if k in
                     _get_figure_keywords())


Test patch:
diff --git a/dask/diagnostics/tests/test_profiler.py b/dask/diagnostics/tests/test_profiler.py
--- a/dask/diagnostics/tests/test_profiler.py
+++ b/dask/diagnostics/tests/test_profiler.py
@@ -245,13 +245,21 @@ def test_resource_profiler_plot():
     assert len(p.tools) == 1
     assert isinstance(p.tools[0], bokeh.models.HoverTool)
     assert check_title(p, "Not the default")
-    # Test empty, checking for errors
-    rprof.clear()
-
-    with pytest.warns(None) as record:
-        rprof.visualize(show=False, save=False)
 
-    assert len(record) == 0
+    # Test with empty and one point, checking for errors
+    rprof.clear()
+    for results in [[], [(1.0, 0, 0)]]:
+        rprof.results = results
+        with pytest.warns(None) as record:
+            p = rprof.visualize(show=False, save=False)
+        assert len(record) == 0
+        # Check bounds are valid
+        assert p.x_range.start == 0
+        assert p.x_range.end == 1
+        assert p.y_range.start == 0
+        assert p.y_range.end == 100
+        assert p.extra_y_ranges['memory'].start == 0
+        assert p.extra_y_ranges['memory'].end == 100
 
 
 @pytest.mark.skipif("not bokeh")