diff options
Diffstat (limited to 'resources/tools/dash/app/pal/trending')
-rw-r--r-- | resources/tools/dash/app/pal/trending/data.py | 13 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/trending/html_layout.txt | 29 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/trending/layout.py | 364 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/trending/spec_test_selection.yaml | 106 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/trending/trending.py (renamed from resources/tools/dash/app/pal/trending/dashboard.py) | 47 |
5 files changed, 505 insertions, 54 deletions
diff --git a/resources/tools/dash/app/pal/trending/data.py b/resources/tools/dash/app/pal/trending/data.py index 06466a9175..298a40542e 100644 --- a/resources/tools/dash/app/pal/trending/data.py +++ b/resources/tools/dash/app/pal/trending/data.py @@ -11,14 +11,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Prepare data for Plotly Dash.""" +"""Prepare data for Plotly Dash. +""" import pandas as pd def create_dataframe(): - """Create Pandas DataFrame from local CSV.""" + """Create Pandas DataFrame from local CSV. + + DEMO + """ + return pd.read_csv( - "https://s3-docs.fd.io/csit/master/trending/_static/vpp/" - "csit-vpp-perf-mrr-daily-master-2n-zn2-trending.csv" + u"https://s3-docs.fd.io/csit/master/trending/_static/vpp/" + u"csit-vpp-perf-mrr-daily-master-2n-zn2-trending.csv" ) diff --git a/resources/tools/dash/app/pal/trending/html_layout.txt b/resources/tools/dash/app/pal/trending/html_layout.txt new file mode 100644 index 0000000000..3f0aa0cb8d --- /dev/null +++ b/resources/tools/dash/app/pal/trending/html_layout.txt @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> + <head> + {%metas%} + <title>Continuous Performance Trending</title> + {%favicon%} + {%css%} + </head> + <body class="dash-template"> + <header> + <div class="nav-wrapper"> + <a href="/"> + <h1>FD.io CSIT</h1> + </a> + <a href=""> + <h1>Continuous Performance Trending</h1> + </a> + <nav> + </nav> + </div> + </header> + {%app_entry%} + <footer> + {%config%} + {%scripts%} + {%renderer%} + </footer> + </body> +</html> diff --git a/resources/tools/dash/app/pal/trending/layout.py b/resources/tools/dash/app/pal/trending/layout.py index 1f60aecd83..69ce0c416d 100644 --- a/resources/tools/dash/app/pal/trending/layout.py +++ b/resources/tools/dash/app/pal/trending/layout.py @@ -11,33 +11,339 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Plotly Dash HTML layout override.""" - -html_layout = """ -<!DOCTYPE html> - <html> - <head> - {%metas%} - <title>{%title%}</title> - {%favicon%} - {%css%} - </head> - <body class="dash-template"> - <header> - <div class="nav-wrapper"> - <a href="/"> - <h1>Continuous Performance Trending</h1> - </a> - <nav> - </nav> - </div> - </header> - {%app_entry%} - <footer> - {%config%} - {%scripts%} - {%renderer%} - </footer> - </body> - </html> +"""Plotly Dash HTML layout override. """ + +import logging + +from dash import dcc +from dash import html +from dash import Input, Output, callback +from dash.exceptions import PreventUpdate +from yaml import load, FullLoader, YAMLError + + +class Layout: + """ + """ + + def __init__(self, app, html_layout_file, spec_file): + """ + """ + + # Inputs + self._app = app + self._html_layout_file = html_layout_file + self._spec_file = spec_file + + # Read from files: + self._html_layout = "" + self._spec_test = None + + try: + with open(self._html_layout_file, "r") as layout_file: + self._html_layout = layout_file.read() + except IOError as err: + logging.error(f"Not possible to open the file {layout_file}\n{err}") + + try: + with open(self._spec_file, "r") as file_read: + self._spec_test = load(file_read, Loader=FullLoader) + except IOError as err: + logging.error(f"Not possible to open the file {spec_file}\n{err}") + except YAMLError as err: + logging.error( + f"An error occurred while parsing the specification file " + f"{spec_file}\n" + f"{err}" + ) + + # Callbacks: + if self._app is not None and hasattr(self, 'callbacks'): + self.callbacks(self._app) + + # User choice (one test): + self._test_selection = { + "phy": "", + "area": "", + "test": "", + "core": "", + "frame-size": "", + "test-type": "" + } + + @property + def html_layout(self): + return self._html_layout + + @property + def spec_test(self): + return self._spec_test + + def _reset_test_selection(self): + self._test_selection = { + "phy": "", + "area": "", + "test": "", + "core": "", + "frame-size": "", + "test-type": "" + } + + def add_content(self): + """ + """ + if self._html_layout and self._spec_test: + return html.Div( + id="div-main", + children=[ + self._add_ctrl_div(), + self._add_plotting_div() + ] + ) + else: + return html.Div( + id="div-main-error", + children="An Error Occured." + ) + + def _add_ctrl_div(self): + """Add div with controls. It is placed on the left side. + """ + return html.Div( + id="div-controls", + children=[ + html.Div( + id="div-controls-tabs", + children=[ + self._add_ctrl_select(), + self._add_ctrl_shown() + ] + ) + ], + style={ + "display": "inline-block", + "width": "18%", + "padding": "5px" + } + ) + + def _add_plotting_div(self): + """Add div with plots and tables. It is placed on the right side. + """ + return html.Div( + id="div-plotting-area", + children=[ + # Only a visible note. + # TODO: Add content. + html.H3( + "Graphs and Tables", + style={ + "vertical-align": "middle", + "text-align": "center" + } + ) + ], + style={ + "vertical-align": "middle", + "display": "inline-block", + "width": "80%", + "padding": "5px" + } + ) + + def _add_ctrl_shown(self): + """ + """ + return html.Div( + id="div-ctrl-shown", + children="List of selected tests" + ) + + def _add_ctrl_select(self): + """ + """ + return html.Div( + id="div-ctrl-select", + children=[ + html.Br(), + html.Div( + children="Physical Test Bed Topology, NIC and Driver" + ), + dcc.Dropdown( + id="dd-ctrl-phy", + placeholder="Select a Physical Test Bed Topology...", + multi=False, + clearable=False, + options=[ + {"label": k, "value": k} for k in self._spec_test.keys() + ], + ), + html.Br(), + html.Div( + children="Area" + ), + dcc.Dropdown( + id="dd-ctrl-area", + placeholder="Select an Area...", + disabled=True, + multi=False, + clearable=False, + ), + html.Br(), + html.Div( + children="Test" + ), + dcc.Dropdown( + id="dd-ctrl-test", + placeholder="Select a Test...", + disabled=True, + multi=False, + clearable=False, + ), + + # Change to radio buttons: + html.Br(), + html.Div( + children="Number of Cores" + ), + dcc.Dropdown( + id="dd-ctrl-core", + placeholder="Select a Number of Cores...", + disabled=True, + multi=False, + clearable=False, + ), + html.Br(), + html.Div( + children="Frame Size" + ), + dcc.Dropdown( + id="dd-ctrl-framesize", + placeholder="Select a Frame Size...", + disabled=True, + multi=False, + clearable=False, + ), + html.Br(), + html.Div( + children="Test Type" + ), + dcc.Dropdown( + id="dd-ctrl-testtype", + placeholder="Select a Test Type...", + disabled=True, + multi=False, + clearable=False, + ), + html.Br(), + html.Button( + id="btn-ctrl-add", + children="Add", + disabled=True + ), + html.Br(), + html.Div( + id="div-ctrl-info" + ) + ] + ) + + def callbacks(self, app): + + @app.callback( + Output("dd-ctrl-area", "options"), + Output("dd-ctrl-area", "disabled"), + Input("dd-ctrl-phy", "value"), + ) + def _update_dd_area(phy): + """ + """ + + if phy is None: + raise PreventUpdate + + try: + options = [ + {"label": self._spec_test[phy][v]["label"], "value": v} + for v in [v for v in self._spec_test[phy].keys()] + ] + except KeyError: + options = list() + + return options, False + + @app.callback( + Output("dd-ctrl-test", "options"), + Output("dd-ctrl-test", "disabled"), + Input("dd-ctrl-phy", "value"), + Input("dd-ctrl-area", "value"), + ) + def _update_dd_test(phy, area): + """ + """ + + if not all((phy, area, )): + raise PreventUpdate + + try: + options = [ + {"label": v, "value": v} + for v in self._spec_test[phy][area]["test"] + ] + except KeyError: + options = list() + + return options, False + + @app.callback( + Output("btn-ctrl-add", "disabled"), + Input("dd-ctrl-phy", "value"), + Input("dd-ctrl-area", "value"), + Input("dd-ctrl-test", "value"), + ) + def _update_btn_add(phy, area, test): + """ + """ + + if all((phy, area, test, )): + self._test_selection["phy"] = phy + self._test_selection["area"] = area + self._test_selection["test"] = test + return False + else: + return True + + @app.callback( + Output("div-ctrl-info", "children"), + Output("dd-ctrl-phy", "value"), + Output("dd-ctrl-area", "value"), + Output("dd-ctrl-test", "value"), + Output("btn-ctrl-add", "n_clicks"), + Input("btn-ctrl-add", "n_clicks") + ) + def _print_user_selection(n_clicks): + """ + """ + + logging.info(f"\n\n{n_clicks}\n\n") + + if not n_clicks: + raise PreventUpdate + + selected = ( + f"{self._test_selection['phy']} # " + f"{self._test_selection['area']} # " + f"{self._test_selection['test']} # " + f"{n_clicks}\n" + ) + + self._reset_test_selection() + + return ( + selected, + None, + None, + None, + 0, + ) diff --git a/resources/tools/dash/app/pal/trending/spec_test_selection.yaml b/resources/tools/dash/app/pal/trending/spec_test_selection.yaml new file mode 100644 index 0000000000..dae902203a --- /dev/null +++ b/resources/tools/dash/app/pal/trending/spec_test_selection.yaml @@ -0,0 +1,106 @@ +2n-aws-nitro-50g-ena: + ip4-base: + label: IPv4 Routing Base + test: + - ethip4-ip4base + core: [1C, 2C] + frame-size: [64B, 1518B] + test-type: [MRR, NDR, PDR] + ip4-scale: + label: IPv4 Routing Scale + test: + - ethip4-ip4scale20k + - ethip4-ip4scale20k-rnd + core: [1C, 2C] + frame-size: [64B, 1518B] + test-type: [MRR, NDR, PDR] + ip6-base: + label: IPv6 Routing Base + test: + - ethip6-ip4base + core: [1C, 2C] + frame-size: [78B, 1518B] + test-type: [MRR, NDR, PDR] + ip6-scale: + label: IPv6 Routing Scale + test: + - ethip6-ip4scale20k + - ethip6-ip4scale20k-rnd + core: [1C, 2C] + frame-size: [78B, 1518B] + test-type: [MRR, NDR, PDR] +# 2n-clx-cx556a-rdma: +# 2n-clx-x710-avf: +# 2n-clx-x710-dpdk: +# 2n-clx-xxv710-af-xdp: +# 2n-clx-xxv710-avf: +# l2-base: +# l2-scale: +# ip4-base: +# ip4-scale: +# ip4-features: +# ip6-base: +# ip6-scale: +# ethip4-ethip4udpgeneve: +# nat44det-ip4-stl-bidir: +# nat44ed-ip4-stl-unidir: +# nat44ed-ip4-udp-stf-cps: +# nat44ed-ip4-tcp-stf-cps: +# nat44ed-ip4-udp-stf-pps: +# nat44ed-ip4-tcp-stf-pps: +# nat44ed-ip4-udp-tput: +# nat44ed-ip4-tcp-tput: +# vhost-base: +# memif-base: +# vnf-service-chains-routing: +# cnf-service-chains-routing: +# cnf-service-pipelines-routing: +# vnf-service-chains-tunnels: +# 2n-clx-xxv710-dpdk: +# 2n-dnv-x553-ixgbe: +# 2n-icx-xxv710-avf: +# 2n-icx-xxv710-dpdk: +# 2n-skx-x710-avf: +# 2n-skx-x710-dpdk: +# 2n-skx-xxv710-avf: +# 2n-skx-xxv710-dpdk: +# 2n-tx2-xl710-af-xdp: +# 2n-tx2-xl710-avf: +# 2n-tx2-xl710-dpdk: +# 2n-zn2-x710-avf: +# 2n-zn2-x710-dpdk: +# 2n-zn2-cx556a-rdma: +# 2n-zn2-xxv710-avf: +# 2n-zn2-xxv710-dpdk: +3n-aws-nitro-50g-ena: + ip4-base: + label: IPv4 Routing Base + test: + - ethip4-ip4base + core: [1C, 2C] + frame-size: [64B, 1518B] + test-type: [MRR, NDR, PDR] + + ip4-scale: + label: IPv4 Routing Scale + test: + - ethip4-ip4scale20k + - ethip4-ip4scale20k-rnd + core: [1C, 2C] + frame-size: [64B, 1518B] + test-type: [MRR, NDR, PDR] + + ipsec-base: + label: IPSec IPv4 Routing Base + test: + - ethip4ipsec40tnlsw-ip4base-int-aes256gcm + core: [1C, 2C] + frame-size: [IMIX, 1518B] + test-type: [MRR, NDR, PDR] +# 3n-dnv-x553-ixgbe: +# 3n-icx-xxv710-avf: +# 3n-icx-xxv710-dpdk: +# 3n-skx-x710-avf: +# 3n-skx-xxv710-avf: +# 3n-skx-xxv710-dpdk: +# 3n-tsh-x520-ixgbe: diff --git a/resources/tools/dash/app/pal/trending/dashboard.py b/resources/tools/dash/app/pal/trending/trending.py index ee5ea5123f..4edf831045 100644 --- a/resources/tools/dash/app/pal/trending/dashboard.py +++ b/resources/tools/dash/app/pal/trending/trending.py @@ -11,7 +11,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Instantiate a Dash app.""" +"""Instantiate the Trending Dash applocation. +""" import dash from dash import dcc from dash import html @@ -20,10 +21,10 @@ import numpy as np import pandas as pd from .data import create_dataframe -from .layout import html_layout +from .layout import Layout -def init_dashboard(server): +def init_trending(server): """Create a Plotly Dash dashboard. :param server: Flask server. @@ -31,12 +32,13 @@ def init_dashboard(server): :returns: Dash app server. :rtype: Dash """ + dash_app = dash.Dash( server=server, - routes_pathname_prefix="/trending/", + routes_pathname_prefix=u"/trending/", external_stylesheets=[ - "/static/dist/css/styles.css", - "https://fonts.googleapis.com/css?family=Lato", + u"/static/dist/css/styles.css", + u"https://fonts.googleapis.com/css?family=Lato", ], ) @@ -44,26 +46,29 @@ def init_dashboard(server): df = create_dataframe() # Custom HTML layout - dash_app.index_string = html_layout - - # Create Layout - dash_app.layout = html.Div( - children=[ - create_data_table(df), - ], - id="dash-container", + layout = Layout( + app=dash_app, + html_layout_file="pal/trending/html_layout.txt", + spec_file="pal/trending/spec_test_selection.yaml" ) + dash_app.index_string = layout.html_layout + dash_app.layout = layout.add_content() + return dash_app.server def create_data_table(df): - """Create Dash datatable from Pandas DataFrame.""" + """Create Dash datatable from Pandas DataFrame. + + DEMO + """ + table = dash_table.DataTable( - id="database-table", - columns=[{"name": i, "id": i} for i in df.columns], - data=df.to_dict("records"), - sort_action="native", - sort_mode="native", - page_size=300, + id=u"database-table", + columns=[{u"name": i, u"id": i} for i in df.columns], + data=df.to_dict(u"records"), + sort_action=u"native", + sort_mode=u"native", + page_size=10, ) return table |