Skip to content

Commit 3bbe830

Browse files
committed
Add dash example
1 parent 096ce43 commit 3bbe830

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

source-code/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ to create it. There is some material not covered in the presentation as well.
2121
* jupyter
2222
* folium (with conda, use `-c conda-forge`)
2323
* xarray
24+
* dash
2425

2526
## What is it?
2627
* [`db-access`](db-access): illustration of accessing SQLite databases and using
@@ -38,3 +39,4 @@ to create it. There is some material not covered in the presentation as well.
3839
and graph representation using networkx.
3940
* [`xarray`](xarray): illustrates the xarray library for pandas-like operations
4041
on multi-dimensional arrays.
42+
* [`dash`](dash): illustration of creating a simple dashboard using dash.

source-code/dash/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# data files are generated
2+
*.csv

source-code/dash/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# dash
2+
3+
The dash framework lets you make dashboards in a convenient way. The HTML, CSS and
4+
Javascript components are generated automatically.
5+
6+
## What is it?
7+
8+
1. `generate_data.py`: script to generate data. It simulates a scheduler.
9+
1. `jobs_overview.py`: script that generatees a dashboard that provides a dynamic
10+
view on the jobs' performance.

source-code/dash/jobs_overview.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env python
2+
3+
from argparse import ArgumentParser
4+
import dash
5+
import dash_core_components as dcc
6+
import dash_html_components as html
7+
from dash.dependencies import Input, Output
8+
from datetime import datetime
9+
import pandas as pd
10+
11+
12+
def generate_overview_table(jobs):
13+
jobs_summary = jobs[['job_id', 'node']]\
14+
.drop_duplicates()\
15+
.groupby('job_id')\
16+
.count()
17+
jobs_summary = jobs_summary.merge(jobs.groupby('job_id')[['time']].min(),
18+
on='job_id')
19+
jobs_summary = jobs_summary.merge(jobs.groupby('job_id')[['time']].max(),
20+
on='job_id')
21+
jobs_summary.columns = ['nodes', 'start time', 'end time']
22+
jobs_summary.insert(0, 'job_id', jobs_summary.index)
23+
return html.Table([
24+
html.Thead(
25+
html.Tr([html.Th(column) for column in jobs_summary.columns])
26+
),
27+
html.Tbody([
28+
html.Tr([
29+
html.Td(jobs_summary.loc[job_id][column])
30+
for column in jobs_summary.columns])
31+
for job_id in jobs_summary.index]
32+
)]
33+
)
34+
35+
def generate_job_menu(jobs):
36+
job_list = [{'label': str(job_id), 'value': str(job_id)}
37+
for job_id in jobs.job_id.unique()]
38+
if len(job_list):
39+
return dcc.Dropdown(
40+
id='job_menu',
41+
options=job_list,
42+
value=job_list[0]['value'],
43+
placeholder='Select a job...',
44+
)
45+
46+
47+
48+
if __name__ == '__main__':
49+
arg_parsr = ArgumentParser(description='visualize job performance')
50+
arg_parsr.add_argument('--job-file', required=True,
51+
help='file with job/node information')
52+
arg_parsr.add_argument('--load-file', required=True,
53+
help='file with node/load information')
54+
options = arg_parsr.parse_args()
55+
jobs = pd.read_csv(options.job_file, parse_dates=True,
56+
date_parser=pd.to_datetime)
57+
loads = pd.read_csv(options.load_file, parse_dates=True,
58+
date_parser=pd.to_datetime)
59+
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
60+
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
61+
app.layout = html.Div(children=[
62+
html.H1(children='Job overview'),
63+
html.Div(children=[
64+
html.Div(children=[generate_overview_table(jobs)],
65+
style={'width': '70%', 'display': 'inline-block',
66+
'margin': '20pt'}),
67+
html.Div(children=[
68+
html.Label('Jobs'),
69+
generate_job_menu(jobs),
70+
], style={'width': '10%', 'float': 'left', 'display': 'inline-block',
71+
'margin': '20pt'})
72+
]),
73+
html.Div(children=[
74+
html.Div(children=[ dcc.Graph(id='cpu_load_graph')],
75+
style={'width': '45%', 'display': 'inline-block'}),
76+
html.Div(children=[dcc.Graph(id='mem_load_graph')],
77+
style={'widht': '45%', 'float': 'right', 'display': 'inline-block'})
78+
])
79+
])
80+
81+
@app.callback(
82+
[Output('cpu_load_graph', 'figure'), Output('mem_load_graph', 'figure')],
83+
[Input('job_menu', 'value')])
84+
def update(job_id):
85+
data = loads.merge(jobs.query(f'job_id == {job_id}'), on=['time', 'node'],
86+
how='right')
87+
figures = list()
88+
for quantity in ['cpu', 'mem']:
89+
nodes_data = list()
90+
for node_df in data.groupby('node'):
91+
node_data = dict()
92+
node_data['name'] = node_df[0]
93+
node_data['x'] = node_df[1].time
94+
node_data['y'] = node_df[1][f'{quantity}_load']
95+
node_data['mode'] = ['markers', 'line'],
96+
nodes_data.append(node_data)
97+
figures.append({
98+
'data': nodes_data,
99+
'layout': dict(
100+
xaxis={'title': 'time'},
101+
yaxis={'title': f'{quantity} load'},
102+
hovermode='closest'),
103+
})
104+
return figures
105+
106+
107+
app.run_server(debug=True)

0 commit comments

Comments
 (0)