aboutsummaryrefslogtreecommitdiffstats
path: root/resources/tools/dash/app/pal/trending
diff options
context:
space:
mode:
authorTibor Frank <tifrank@cisco.com>2022-01-26 09:31:43 +0100
committerTibor Frank <tifrank@cisco.com>2022-02-24 08:11:41 +0100
commitb2cb835b34c7404b2aaee3ec30700c67537da66d (patch)
treed0a6ce077b6d927565294fcd7301fb727708cd06 /resources/tools/dash/app/pal/trending
parent9eebc3e59028014a8fc151575b99347d2871c097 (diff)
UTI: PoC - Dash application for Trending
- delete old dash demo Change-Id: I3e75c1dc18ee85c1826838af0343a4b779f71754 Signed-off-by: Tibor Frank <tifrank@cisco.com>
Diffstat (limited to 'resources/tools/dash/app/pal/trending')
-rw-r--r--resources/tools/dash/app/pal/trending/data.py13
-rw-r--r--resources/tools/dash/app/pal/trending/html_layout.txt29
-rw-r--r--resources/tools/dash/app/pal/trending/layout.py364
-rw-r--r--resources/tools/dash/app/pal/trending/spec_test_selection.yaml106
-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