Repository: Parsl/parsl

Issue:
Fix resource usage bug in monitoring
We need to protect against zero division errors:
```
[2019-05-03 18:18:01,362] ERROR in app: Exception on /workflow/9294c39d-a64d-4ad5-9ff8-4b8d757c1af1/resource_usage [GET]
Traceback (most recent call last):
  File "/Users/awoodard/software/miniconda3/envs/parsl/lib/python3.7/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/awoodard/software/miniconda3/envs/parsl/lib/python3.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/awoodard/software/miniconda3/envs/parsl/lib/python3.7/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/awoodard/software/miniconda3/envs/parsl/lib/python3.7/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/Users/awoodard/software/miniconda3/envs/parsl/lib/python3.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/awoodard/software/miniconda3/envs/parsl/lib/python3.7/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/awoodard/ci/viz_server/viz_server/views.py", line 145, in workflow_resources
    df_resources, df_task, type='psutil_process_time_user', label='CPU Time Distribution', option='avg'),
  File "/Users/awoodard/ci/viz_server/viz_server/plots/default/workflow_resource_plots.py", line 15, in resource_distribution_plot
    for i in np.arange(min_range, max_range + time_step, time_step):
ZeroDivisionError: float division by zero
```


Discussion:

Resolving patch:
diff --git a/parsl/monitoring/visualization/plots/default/workflow_resource_plots.py b/parsl/monitoring/visualization/plots/default/workflow_resource_plots.py
--- a/parsl/monitoring/visualization/plots/default/workflow_resource_plots.py
+++ b/parsl/monitoring/visualization/plots/default/workflow_resource_plots.py
@@ -11,9 +11,12 @@ def resource_distribution_plot(df_resources, df_task, type='psutil_process_time_
     max_range = max(df_resources[type].astype('float'))
     time_step = (max_range - min_range) / columns
 
-    x_axis = []
-    for i in np.arange(min_range, max_range + time_step, time_step):
-        x_axis.append(i)
+    if min_range == max_range:
+        x_axis = [min_range]
+    else:
+        x_axis = []
+        for i in np.arange(min_range, max_range + time_step, time_step):
+            x_axis.append(i)
 
     apps_dict = dict()
     for i in range(len(df_task)):
@@ -21,10 +24,9 @@ def resource_distribution_plot(df_resources, df_task, type='psutil_process_time_
         apps_dict[row['task_id']] = []
 
     def y_axis_setup():
-        items = []
+        items = [0] * len(x_axis)
 
         for app, tasks in apps_dict.items():
-            tmp = []
             if option == 'avg':
                 task = df_resources[df_resources['task_id'] ==
                                     app][type].astype('float').mean()
@@ -35,8 +37,10 @@ def y_axis_setup():
             for i in range(len(x_axis) - 1):
                 a = task >= x_axis[i]
                 b = task < x_axis[i + 1]
-                tmp.append(a & b)
-            items = np.sum([items, tmp], axis=0)
+                if a and b:
+                    items[i] += 1
+            if task >= x_axis[-1]:
+                items[-1] += 1
         return items
 
     if "memory" not in type:
@@ -46,7 +50,7 @@ def y_axis_setup():
         xaxis = dict(autorange=True,
                      title='Memory usage (bytes)')
     fig = go.Figure(
-        data=[go.Bar(x=x_axis[:-1],
+        data=[go.Bar(x=x_axis,
                      y=y_axis_setup(),
                      name='tasks')],
         layout=go.Layout(xaxis=xaxis,
@@ -63,17 +67,27 @@ def resource_time_series(tasks, type='psutil_process_time_user', label='CPU user
     start = tasks['epoch_time'].min()
     end = tasks['epoch_time'].max()
     tasks['relative_time'] = tasks['epoch_time'] - start
-    bins = pd.cut(tasks['relative_time'], range(
-        0, end - start + 1, step), include_lowest=True)
-
-    df = tasks.groupby(bins, as_index=False)[type].mean()
-    df['time'] = step * df.index
-    fig = go.Figure(
-        data=[go.Scatter(x=df['time'],
-                         y=df[type],
-                         )],
-        layout=go.Layout(xaxis=dict(autorange=True,
-                                    title='Time (seconds)'),
-                         yaxis=dict(title=label),
-                         title=label))
+    if end != start:
+        bins = pd.cut(tasks['relative_time'],
+                      range(0, end - start + 1, step),
+                      include_lowest=True)
+        df = tasks.groupby(bins, as_index=False)[type].mean()
+        df['time'] = step * df.index
+        fig = go.Figure(
+            data=[go.Scatter(x=df['time'],
+                             y=df[type],
+                             )],
+            layout=go.Layout(xaxis=dict(autorange=True,
+                                        title='Time (seconds)'),
+                             yaxis=dict(title=label),
+                             title=label))
+    else:
+        fig = go.Figure(
+            data=[go.Scatter(x=[0],
+                             y=[tasks[type].mean()],
+                             )],
+            layout=go.Layout(xaxis=dict(autorange=True,
+                                        title='Time (seconds)'),
+                             yaxis=dict(title=label),
+                             title=label))
     return plot(fig, show_link=False, output_type="div", include_plotlyjs=False)
diff --git a/parsl/monitoring/visualization/views.py b/parsl/monitoring/visualization/views.py
--- a/parsl/monitoring/visualization/views.py
+++ b/parsl/monitoring/visualization/views.py
@@ -129,6 +129,10 @@ def workflow_resources(workflow_id):
 
     df_resources = pd.read_sql_query(
         "SELECT * FROM resource WHERE run_id='%s'" % (workflow_id), db.engine)
+    if df_resources.empty:
+        return render_template('error.html',
+                               message="Workflow %s does not have any resource usage records." % workflow_id)
+
     df_task = pd.read_sql_query(
         "SELECT * FROM task WHERE run_id='%s'" % (workflow_id), db.engine)
 


Test patch: