aboutsummaryrefslogtreecommitdiffstats
path: root/resources/tools/dash/app/pal
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
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')
-rw-r--r--resources/tools/dash/app/pal/__init__.py39
-rw-r--r--resources/tools/dash/app/pal/assets.py18
-rw-r--r--resources/tools/dash/app/pal/report/data.py26
-rw-r--r--resources/tools/dash/app/pal/report/layout.py46
-rw-r--r--resources/tools/dash/app/pal/report/report.py (renamed from resources/tools/dash/app/pal/trending/dashboard.py)33
-rw-r--r--resources/tools/dash/app/pal/routes.py18
-rw-r--r--resources/tools/dash/app/pal/templates/index.jinja214
-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.py74
12 files changed, 706 insertions, 74 deletions
diff --git a/resources/tools/dash/app/pal/__init__.py b/resources/tools/dash/app/pal/__init__.py
index 9950defa44..863fd081e7 100644
--- a/resources/tools/dash/app/pal/__init__.py
+++ b/resources/tools/dash/app/pal/__init__.py
@@ -11,29 +11,48 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Initialize Flask app."""
+"""Initialize Flask app.
+"""
+
+import logging
+
from flask import Flask
from flask_assets import Environment
def init_app():
- """Construct core Flask application with embedded Dash app."""
+ """Construct core Flask application with embedded Dash app.
+ """
+
+ logging.basicConfig(
+ format=u"%(asctime)s: %(levelname)s: %(message)s",
+ datefmt=u"%Y/%m/%d %H:%M:%S",
+ level=logging.INFO
+ )
+
+ logging.info("Application started.")
+
app = Flask(__name__, instance_relative_config=False)
- app.config.from_object("config.Config")
- assets = Environment()
- assets.init_app(app)
+ app.config.from_object(u"config.Config")
with app.app_context():
# Import parts of our core Flask app.
from . import routes
from .assets import compile_static_assets
- # Import Trending Dash application.
- from .trending.dashboard import init_dashboard
-
- app = init_dashboard(app)
+ assets = Environment()
+ assets.init_app(app)
# Compile static assets.
compile_static_assets(assets)
- return app
+ # Import Dash applications.
+ from .trending.trending import init_trending
+ app = init_trending(app)
+
+ from .report.report import init_report
+ app = init_report(app)
+
+ return app
+
+app = init_app()
diff --git a/resources/tools/dash/app/pal/assets.py b/resources/tools/dash/app/pal/assets.py
index 4237707734..63fac16333 100644
--- a/resources/tools/dash/app/pal/assets.py
+++ b/resources/tools/dash/app/pal/assets.py
@@ -11,7 +11,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Compile static assets."""
+"""Compile static assets.
+"""
+
from flask import current_app as app
from flask_assets import Bundle
@@ -24,15 +26,17 @@ def compile_static_assets(assets):
:returns: Compiled stylesheets.
:rtype: Environment
"""
+
assets.auto_build = True
assets.debug = False
less_bundle = Bundle(
- "less/*.less",
- filters="less,cssmin",
- output="dist/css/styles.css",
- extra={"rel": "stylesheet/less"},
+ u"less/*.less",
+ filters=u"less,cssmin",
+ output=u"dist/css/styles.css",
+ extra={u"rel": u"stylesheet/less"},
)
- assets.register("less_all", less_bundle)
- if app.config["FLASK_ENV"] == "development":
+ assets.register(u"less_all", less_bundle)
+ if app.config[u"FLASK_ENV"] == u"development":
less_bundle.build()
+
return assets
diff --git a/resources/tools/dash/app/pal/report/data.py b/resources/tools/dash/app/pal/report/data.py
new file mode 100644
index 0000000000..43e0239228
--- /dev/null
+++ b/resources/tools/dash/app/pal/report/data.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2022 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Prepare data for Plotly Dash."""
+
+import pandas as pd
+
+
+def create_dataframe():
+ """Create Pandas DataFrame from local CSV.
+ """
+
+ return pd.read_csv(
+ u"https://s3-docs.fd.io/csit/master/trending/_static/vpp/"
+ u"csit-vpp-perf-mrr-daily-master-2n-skx-trending.csv"
+ )
diff --git a/resources/tools/dash/app/pal/report/layout.py b/resources/tools/dash/app/pal/report/layout.py
new file mode 100644
index 0000000000..70fe727efc
--- /dev/null
+++ b/resources/tools/dash/app/pal/report/layout.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2022 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Plotly Dash HTML layout override."""
+
+html_layout = u"""
+<!DOCTYPE html>
+ <html>
+ <head>
+ {%metas%}
+ <title>{%title%}</title>
+ {%favicon%}
+ {%css%}
+ </head>
+ <body class="dash-template">
+ <header>
+ <div class="nav-wrapper">
+ <a href="/">
+ <h1>FD.io CSIT</h1>
+ </a>
+ <a href="">
+ <h1>Report</h1>
+ </a>
+ <nav>
+ </nav>
+ </div>
+ </header>
+ {%app_entry%}
+ <footer>
+ {%config%}
+ {%scripts%}
+ {%renderer%}
+ </footer>
+ </body>
+ </html>
+"""
diff --git a/resources/tools/dash/app/pal/trending/dashboard.py b/resources/tools/dash/app/pal/report/report.py
index ee5ea5123f..d22a0b6705 100644
--- a/resources/tools/dash/app/pal/trending/dashboard.py
+++ b/resources/tools/dash/app/pal/report/report.py
@@ -11,7 +11,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Instantiate a Dash app."""
+"""Instantiate the Report Dash application.
+"""
+
import dash
from dash import dcc
from dash import html
@@ -23,7 +25,7 @@ from .data import create_dataframe
from .layout import html_layout
-def init_dashboard(server):
+def init_report(server):
"""Create a Plotly Dash dashboard.
:param server: Flask server.
@@ -31,12 +33,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"/report/",
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",
],
)
@@ -51,19 +54,23 @@ def init_dashboard(server):
children=[
create_data_table(df),
],
- id="dash-container",
+ id=u"dash-container",
)
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=5,
)
return table
diff --git a/resources/tools/dash/app/pal/routes.py b/resources/tools/dash/app/pal/routes.py
index 16680c4436..200d7afeba 100644
--- a/resources/tools/dash/app/pal/routes.py
+++ b/resources/tools/dash/app/pal/routes.py
@@ -11,17 +11,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Routes for parent Flask app."""
+"""Routes for parent Flask app.
+"""
+
from flask import current_app as app
from flask import render_template
-@app.route("/")
+@app.route(u"/")
def home():
- """Landing page."""
+ """Landing page.
+ """
+
return render_template(
- "index.jinja2",
- title="FD.io CSIT",
- description="Performance Dashboard",
- template="home-template"
+ u"index.jinja2",
+ title=u"FD.io CSIT",
+ description=u"Performance Dashboard",
+ template=u"home-template"
)
diff --git a/resources/tools/dash/app/pal/templates/index.jinja2 b/resources/tools/dash/app/pal/templates/index.jinja2
index 8ab5c84356..bd53346065 100644
--- a/resources/tools/dash/app/pal/templates/index.jinja2
+++ b/resources/tools/dash/app/pal/templates/index.jinja2
@@ -5,9 +5,15 @@
<img src="{{ url_for('static', filename='img/logo.svg') }}" class="logo" />
<h1 class="site-title">{{ title }}</h1>
<p class="subtitle">{{ description }}</p>
- <a href="/trending/" class="dash-link">
- <span>trending</span>
- <i class="fas fa-arrow-right"></i>
- </a>
+ <div>
+ <a href="/trending/" class="dash-link">
+ <span>Trending</span>
+ <i class="fas fa-arrow-right"></i>
+ </a>
+ <a href="/report/" class="dash-link">
+ <span>Report</span>
+ <i class="fas fa-arrow-right"></i>
+ </a>
+ </div>
</div>
{% endblock %}
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/trending.py b/resources/tools/dash/app/pal/trending/trending.py
new file mode 100644
index 0000000000..4edf831045
--- /dev/null
+++ b/resources/tools/dash/app/pal/trending/trending.py
@@ -0,0 +1,74 @@
+# Copyright (c) 2022 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Instantiate the Trending Dash applocation.
+"""
+import dash
+from dash import dcc
+from dash import html
+from dash import dash_table
+import numpy as np
+import pandas as pd
+
+from .data import create_dataframe
+from .layout import Layout
+
+
+def init_trending(server):
+ """Create a Plotly Dash dashboard.
+
+ :param server: Flask server.
+ :type server: Flask
+ :returns: Dash app server.
+ :rtype: Dash
+ """
+
+ dash_app = dash.Dash(
+ server=server,
+ routes_pathname_prefix=u"/trending/",
+ external_stylesheets=[
+ u"/static/dist/css/styles.css",
+ u"https://fonts.googleapis.com/css?family=Lato",
+ ],
+ )
+
+ # Load DataFrame
+ df = create_dataframe()
+
+ # Custom HTML layout
+ 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.
+
+ DEMO
+ """
+
+ table = dash_table.DataTable(
+ 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