1 # Copyright (c) 2022 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 """Plotly Dash HTML layout override.
19 import dash_bootstrap_components as dbc
21 from flask import Flask
24 from dash import callback_context, no_update, ALL
25 from dash import Input, Output, State
26 from dash.exceptions import PreventUpdate
27 from yaml import load, FullLoader, YAMLError
28 from datetime import datetime, timedelta
29 from copy import deepcopy
30 from json import loads, JSONDecodeError
31 from ast import literal_eval
33 from ..data.data import Data
34 from .graphs import graph_trending, graph_hdrh_latency, \
36 from ..data.url_processing import url_decode, url_encode
43 STYLE_DISABLED = {"display": "none"}
44 STYLE_ENABLED = {"display": "inherit"}
57 PLACEHOLDER = html.Nobr("")
59 DRIVERS = ("avf", "af-xdp", "rdma", "dpdk")
63 "container_memif": "LXC/DRC Container Memif",
64 "crypto": "IPSec IPv4 Routing",
65 "ip4": "IPv4 Routing",
66 "ip6": "IPv6 Routing",
67 "ip4_tunnels": "IPv4 Tunnels",
68 "l2": "L2 Ethernet Switching",
69 "srv6": "SRv6 Routing",
70 "vm_vhost": "VMs vhost-user",
71 "nfv_density-dcr_memif-chain_ipsec": "CNF Service Chains Routing IPSec",
72 "nfv_density-vm_vhost-chain_dot1qip4vxlan":"VNF Service Chains Tunnels",
73 "nfv_density-vm_vhost-chain": "VNF Service Chains Routing",
74 "nfv_density-dcr_memif-pipeline": "CNF Service Pipelines Routing",
75 "nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
79 "background-color": "#d2ebf5",
80 "border-color": "#bce1f1",
84 def __init__(self, app: Flask, html_layout_file: str, spec_file: str,
85 graph_layout_file: str, data_spec_file: str, tooltip_file: str,
86 time_period: str=None) -> None:
92 self._html_layout_file = html_layout_file
93 self._spec_file = spec_file
94 self._graph_layout_file = graph_layout_file
95 self._data_spec_file = data_spec_file
96 self._tooltip_file = tooltip_file
97 self._time_period = time_period
101 data_spec_file=self._data_spec_file,
103 ).read_trending_mrr(days=self._time_period)
106 data_spec_file=self._data_spec_file,
108 ).read_trending_ndrpdr(days=self._time_period)
110 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
113 (datetime.utcnow() - self._data["start_time"].min()).days
114 if self._time_period > data_time_period:
115 self._time_period = data_time_period
118 # Get structure of tests:
120 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
122 lst_job = row["job"].split("-")
125 tbed = "-".join(lst_job[-2:])
126 lst_test = row["test_id"].split(".")
130 area = "-".join(lst_test[3:-2])
131 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
134 nic = suite.split("-")[0]
135 for drv in self.DRIVERS:
141 test = test.replace(f"{drv}-", "")
145 infra = "-".join((tbed, nic, driver))
146 lst_test = test.split("-")
147 framesize = lst_test[0]
148 core = lst_test[1] if lst_test[1] else "8C"
149 test = "-".join(lst_test[2: -1])
151 if tbs.get(dut, None) is None:
153 if tbs[dut].get(infra, None) is None:
154 tbs[dut][infra] = dict()
155 if tbs[dut][infra].get(area, None) is None:
156 tbs[dut][infra][area] = dict()
157 if tbs[dut][infra][area].get(test, None) is None:
158 tbs[dut][infra][area][test] = dict()
159 tbs[dut][infra][area][test]["core"] = list()
160 tbs[dut][infra][area][test]["frame-size"] = list()
161 tbs[dut][infra][area][test]["test-type"] = list()
162 if core.upper() not in tbs[dut][infra][area][test]["core"]:
163 tbs[dut][infra][area][test]["core"].append(core.upper())
164 if framesize.upper() not in \
165 tbs[dut][infra][area][test]["frame-size"]:
166 tbs[dut][infra][area][test]["frame-size"].append(
169 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
170 tbs[dut][infra][area][test]["test-type"].append("MRR")
171 elif ttype == "ndrpdr":
172 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
173 tbs[dut][infra][area][test]["test-type"].extend(
178 self._html_layout = ""
179 self._graph_layout = None
180 self._tooltips = dict()
183 with open(self._html_layout_file, "r") as file_read:
184 self._html_layout = file_read.read()
185 except IOError as err:
187 f"Not possible to open the file {self._html_layout_file}\n{err}"
191 with open(self._graph_layout_file, "r") as file_read:
192 self._graph_layout = load(file_read, Loader=FullLoader)
193 except IOError as err:
195 f"Not possible to open the file {self._graph_layout_file}\n"
198 except YAMLError as err:
200 f"An error occurred while parsing the specification file "
201 f"{self._graph_layout_file}\n{err}"
205 with open(self._tooltip_file, "r") as file_read:
206 self._tooltips = load(file_read, Loader=FullLoader)
207 except IOError as err:
209 f"Not possible to open the file {self._tooltip_file}\n{err}"
211 except YAMLError as err:
213 f"An error occurred while parsing the specification file "
214 f"{self._tooltip_file}\n{err}"
218 if self._app is not None and hasattr(self, 'callbacks'):
219 self.callbacks(self._app)
222 def html_layout(self):
223 return self._html_layout
227 return self._spec_tbs
235 return self._graph_layout
238 def time_period(self):
239 return self._time_period
241 def label(self, key: str) -> str:
242 return self.LABELS.get(key, key)
244 def _show_tooltip(self, id: str, title: str,
245 clipboard_id: str=None) -> list:
249 dcc.Clipboard(target_id=clipboard_id, title="Copy URL") \
250 if clipboard_id else str(),
258 class_name="border ms-1",
261 children=self._tooltips.get(id, str()),
267 def add_content(self):
270 if self.html_layout and self.spec_tbs:
284 id="offcanvas-metadata",
285 title="Throughput And Latency",
289 dbc.Row(id="metadata-tput-lat"),
290 dbc.Row(id="metadata-hdrh-graph"),
298 dcc.Store(id="selected-tests"),
299 dcc.Store(id="control-panel"),
300 dcc.Location(id="url", refresh=False),
301 self._add_ctrl_col(),
302 self._add_plotting_col(),
320 def _add_navbar(self):
321 """Add nav element with navigation panel. It is placed on the top.
323 return dbc.NavbarSimple(
324 id="navbarsimple-main",
328 "Continuous Performance Trending",
337 brand_external_link=True,
342 def _add_ctrl_col(self) -> dbc.Col:
343 """Add column with controls. It is placed on the left side.
348 self._add_ctrl_panel(),
352 def _add_plotting_col(self) -> dbc.Col:
353 """Add column with plots and tables. It is placed on the right side.
356 id="col-plotting-area",
360 dbc.Row( # Throughput
362 class_name="g-0 p-2",
369 class_name="g-0 p-2",
375 id="row-btn-download",
376 class_name="g-0 p-2",
387 def _add_ctrl_panel(self) -> dbc.Row:
392 class_name="g-0 p-2",
400 children=self._show_tooltip(
406 "Select a Device under Test..."
410 {"label": k, "value": k} \
411 for k in self.spec_tbs.keys()
413 key=lambda d: d["label"]
428 children=self._show_tooltip(
429 "help-infra", "Infra")
434 "Select a Physical Test Bed "
450 children=self._show_tooltip(
455 placeholder="Select an Area...",
470 children=self._show_tooltip(
475 placeholder="Select a Test...",
485 id="row-ctrl-framesize",
489 children=self._show_tooltip(
490 "help-framesize", "Frame Size"),
496 id="cl-ctrl-framesize-all",
497 options=self.CL_ALL_DISABLED,
507 id="cl-ctrl-framesize",
520 children=self._show_tooltip(
521 "help-cores", "Number of Cores"),
527 id="cl-ctrl-core-all",
528 options=self.CL_ALL_DISABLED,
547 id="row-ctrl-testtype",
551 children=self._show_tooltip(
552 "help-ttype", "Test Type"),
558 id="cl-ctrl-testtype-all",
559 options=self.CL_ALL_DISABLED,
569 id="cl-ctrl-testtype",
578 class_name="gy-1 p-0",
584 children="Add Selected",
598 children=self._show_tooltip(
599 "help-time-period", "Time Period"),
603 className="d-flex justify-content-center",
605 datetime.utcnow() - timedelta(
606 days=self.time_period),
607 max_date_allowed=datetime.utcnow(),
608 initial_visible_month=datetime.utcnow(),
610 datetime.utcnow() - timedelta(
611 days=self.time_period),
612 end_date=datetime.utcnow(),
613 display_format="D MMM YY"
618 id="row-card-sel-tests",
620 style=self.STYLE_DISABLED,
627 class_name="overflow-auto",
631 style={"max-height": "12em"},
636 id="row-btns-sel-tests",
637 style=self.STYLE_DISABLED,
644 children="Remove Selected",
645 class_name="w-100 me-1",
650 id="btn-sel-remove-all",
651 children="Remove All",
652 class_name="w-100 me-1",
665 def __init__(self, panel: dict) -> None:
673 # Defines also the order of keys
675 "dd-ctrl-dut-value": str(),
676 "dd-ctrl-phy-options": list(),
677 "dd-ctrl-phy-disabled": True,
678 "dd-ctrl-phy-value": str(),
679 "dd-ctrl-area-options": list(),
680 "dd-ctrl-area-disabled": True,
681 "dd-ctrl-area-value": str(),
682 "dd-ctrl-test-options": list(),
683 "dd-ctrl-test-disabled": True,
684 "dd-ctrl-test-value": str(),
685 "cl-ctrl-core-options": list(),
686 "cl-ctrl-core-value": list(),
687 "cl-ctrl-core-all-value": list(),
688 "cl-ctrl-core-all-options": CL_ALL_DISABLED,
689 "cl-ctrl-framesize-options": list(),
690 "cl-ctrl-framesize-value": list(),
691 "cl-ctrl-framesize-all-value": list(),
692 "cl-ctrl-framesize-all-options": CL_ALL_DISABLED,
693 "cl-ctrl-testtype-options": list(),
694 "cl-ctrl-testtype-value": list(),
695 "cl-ctrl-testtype-all-value": list(),
696 "cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
697 "btn-ctrl-add-disabled": True,
698 "cl-selected-options": list(),
701 self._panel = deepcopy(self._defaults)
703 for key in self._defaults:
704 self._panel[key] = panel[key]
707 def defaults(self) -> dict:
708 return self._defaults
711 def panel(self) -> dict:
714 def set(self, kwargs: dict) -> None:
715 for key, val in kwargs.items():
716 if key in self._panel:
717 self._panel[key] = val
719 raise KeyError(f"The key {key} is not defined.")
721 def get(self, key: str) -> any:
722 return self._panel[key]
724 def values(self) -> tuple:
725 return tuple(self._panel.values())
728 def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
731 options = {v["value"] for v in opt}
733 sel = list(options) if all else list()
735 all = ["all", ] if set(sel) == options else list()
739 def _list_tests(selection: dict) -> list:
740 """Display selected tests with checkboxes
743 return [{"label": v["id"], "value": v["id"]} for v in selection]
748 def _get_date(s_date: str) -> datetime:
749 return datetime(int(s_date[0:4]), int(s_date[5:7]), int(s_date[8:10]))
751 def callbacks(self, app):
753 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
757 (fig_tput, fig_lat) = figs
759 row_fig_tput = self.PLACEHOLDER
760 row_fig_lat = self.PLACEHOLDER
761 row_btn_dwnld = self.PLACEHOLDER
766 id={"type": "graph", "index": "tput"},
774 dcc.Loading(children=[
776 id="btn-download-data",
777 children=self._show_tooltip(
778 "help-download", "Download Data"),
782 dcc.Download(id="download-data")
793 style=self.URL_STYLE,
794 children=self._show_tooltip(
795 "help-url", "URL", "input-url")
801 style=self.URL_STYLE,
812 id={"type": "graph", "index": "lat"},
817 return row_fig_tput, row_fig_lat, row_btn_dwnld
820 Output("control-panel", "data"), # Store
821 Output("selected-tests", "data"), # Store
822 Output("row-graph-tput", "children"),
823 Output("row-graph-lat", "children"),
824 Output("row-btn-download", "children"),
825 Output("row-card-sel-tests", "style"),
826 Output("row-btns-sel-tests", "style"),
827 Output("dd-ctrl-dut", "value"),
828 Output("dd-ctrl-phy", "options"),
829 Output("dd-ctrl-phy", "disabled"),
830 Output("dd-ctrl-phy", "value"),
831 Output("dd-ctrl-area", "options"),
832 Output("dd-ctrl-area", "disabled"),
833 Output("dd-ctrl-area", "value"),
834 Output("dd-ctrl-test", "options"),
835 Output("dd-ctrl-test", "disabled"),
836 Output("dd-ctrl-test", "value"),
837 Output("cl-ctrl-core", "options"),
838 Output("cl-ctrl-core", "value"),
839 Output("cl-ctrl-core-all", "value"),
840 Output("cl-ctrl-core-all", "options"),
841 Output("cl-ctrl-framesize", "options"),
842 Output("cl-ctrl-framesize", "value"),
843 Output("cl-ctrl-framesize-all", "value"),
844 Output("cl-ctrl-framesize-all", "options"),
845 Output("cl-ctrl-testtype", "options"),
846 Output("cl-ctrl-testtype", "value"),
847 Output("cl-ctrl-testtype-all", "value"),
848 Output("cl-ctrl-testtype-all", "options"),
849 Output("btn-ctrl-add", "disabled"),
850 Output("cl-selected", "options"), # User selection
851 State("control-panel", "data"), # Store
852 State("selected-tests", "data"), # Store
853 State("cl-selected", "value"), # User selection
854 Input("dd-ctrl-dut", "value"),
855 Input("dd-ctrl-phy", "value"),
856 Input("dd-ctrl-area", "value"),
857 Input("dd-ctrl-test", "value"),
858 Input("cl-ctrl-core", "value"),
859 Input("cl-ctrl-core-all", "value"),
860 Input("cl-ctrl-framesize", "value"),
861 Input("cl-ctrl-framesize-all", "value"),
862 Input("cl-ctrl-testtype", "value"),
863 Input("cl-ctrl-testtype-all", "value"),
864 Input("btn-ctrl-add", "n_clicks"),
865 Input("dpr-period", "start_date"),
866 Input("dpr-period", "end_date"),
867 Input("btn-sel-remove", "n_clicks"),
868 Input("btn-sel-remove-all", "n_clicks"),
871 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
872 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
873 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
874 cl_testtype: list, cl_testtype_all: list, btn_add: int,
875 d_start: str, d_end: str, btn_remove: int,
876 btn_remove_all: int, href: str) -> tuple:
880 def _gen_new_url(parsed_url: dict, store_sel: list,
881 start: datetime, end: datetime) -> str:
884 new_url = url_encode({
885 "scheme": parsed_url["scheme"],
886 "netloc": parsed_url["netloc"],
887 "path": parsed_url["path"],
889 "store_sel": store_sel,
899 ctrl_panel = self.ControlPanel(cp_data)
901 d_start = self._get_date(d_start)
902 d_end = self._get_date(d_end)
905 parsed_url = url_decode(href)
907 row_fig_tput = no_update
908 row_fig_lat = no_update
909 row_btn_dwnld = no_update
910 row_card_sel_tests = no_update
911 row_btns_sel_tests = no_update
913 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
915 if trigger_id == "dd-ctrl-dut":
919 {"label": v, "value": v}
920 for v in self.spec_tbs[dd_dut].keys()
922 key=lambda d: d["label"]
929 "dd-ctrl-dut-value": dd_dut,
930 "dd-ctrl-phy-value": str(),
931 "dd-ctrl-phy-options": options,
932 "dd-ctrl-phy-disabled": disabled,
933 "dd-ctrl-area-value": str(),
934 "dd-ctrl-area-options": list(),
935 "dd-ctrl-area-disabled": True,
936 "dd-ctrl-test-options": list(),
937 "dd-ctrl-test-disabled": True,
938 "cl-ctrl-core-options": list(),
939 "cl-ctrl-core-value": list(),
940 "cl-ctrl-core-all-value": list(),
941 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
942 "cl-ctrl-framesize-options": list(),
943 "cl-ctrl-framesize-value": list(),
944 "cl-ctrl-framesize-all-value": list(),
945 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
946 "cl-ctrl-testtype-options": list(),
947 "cl-ctrl-testtype-value": list(),
948 "cl-ctrl-testtype-all-value": list(),
949 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
951 elif trigger_id == "dd-ctrl-phy":
953 dut = ctrl_panel.get("dd-ctrl-dut-value")
956 {"label": self.label(v), "value": v}
957 for v in self.spec_tbs[dut][dd_phy].keys()
959 key=lambda d: d["label"]
966 "dd-ctrl-phy-value": dd_phy,
967 "dd-ctrl-area-value": str(),
968 "dd-ctrl-area-options": options,
969 "dd-ctrl-area-disabled": disabled,
970 "dd-ctrl-test-options": list(),
971 "dd-ctrl-test-disabled": True,
972 "cl-ctrl-core-options": list(),
973 "cl-ctrl-core-value": list(),
974 "cl-ctrl-core-all-value": list(),
975 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
976 "cl-ctrl-framesize-options": list(),
977 "cl-ctrl-framesize-value": list(),
978 "cl-ctrl-framesize-all-value": list(),
979 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
980 "cl-ctrl-testtype-options": list(),
981 "cl-ctrl-testtype-value": list(),
982 "cl-ctrl-testtype-all-value": list(),
983 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
985 elif trigger_id == "dd-ctrl-area":
987 dut = ctrl_panel.get("dd-ctrl-dut-value")
988 phy = ctrl_panel.get("dd-ctrl-phy-value")
991 {"label": v, "value": v}
992 for v in self.spec_tbs[dut][phy][dd_area].keys()
994 key=lambda d: d["label"]
1001 "dd-ctrl-area-value": dd_area,
1002 "dd-ctrl-test-value": str(),
1003 "dd-ctrl-test-options": options,
1004 "dd-ctrl-test-disabled": disabled,
1005 "cl-ctrl-core-options": list(),
1006 "cl-ctrl-core-value": list(),
1007 "cl-ctrl-core-all-value": list(),
1008 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
1009 "cl-ctrl-framesize-options": list(),
1010 "cl-ctrl-framesize-value": list(),
1011 "cl-ctrl-framesize-all-value": list(),
1012 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
1013 "cl-ctrl-testtype-options": list(),
1014 "cl-ctrl-testtype-value": list(),
1015 "cl-ctrl-testtype-all-value": list(),
1016 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
1018 elif trigger_id == "dd-ctrl-test":
1020 framesize_opts = list()
1021 testtype_opts = list()
1022 dut = ctrl_panel.get("dd-ctrl-dut-value")
1023 phy = ctrl_panel.get("dd-ctrl-phy-value")
1024 area = ctrl_panel.get("dd-ctrl-area-value")
1025 cores = self.spec_tbs[dut][phy][area][dd_test]["core"]
1026 fsizes = self.spec_tbs[dut][phy][area][dd_test]["frame-size"]
1027 ttypes = self.spec_tbs[dut][phy][area][dd_test]["test-type"]
1028 if dut and phy and area and dd_test:
1030 {"label": v, "value": v} for v in sorted(cores)
1033 {"label": v, "value": v} for v in sorted(fsizes)
1036 {"label": v, "value": v}for v in sorted(ttypes)
1039 "dd-ctrl-test-value": dd_test,
1040 "cl-ctrl-core-options": core_opts,
1041 "cl-ctrl-core-value": list(),
1042 "cl-ctrl-core-all-value": list(),
1043 "cl-ctrl-core-all-options": self.CL_ALL_ENABLED,
1044 "cl-ctrl-framesize-options": framesize_opts,
1045 "cl-ctrl-framesize-value": list(),
1046 "cl-ctrl-framesize-all-value": list(),
1047 "cl-ctrl-framesize-all-options": self.CL_ALL_ENABLED,
1048 "cl-ctrl-testtype-options": testtype_opts,
1049 "cl-ctrl-testtype-value": list(),
1050 "cl-ctrl-testtype-all-value": list(),
1051 "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
1053 elif trigger_id == "cl-ctrl-core":
1054 val_sel, val_all = self._sync_checklists(
1055 opt=ctrl_panel.get("cl-ctrl-core-options"),
1061 "cl-ctrl-core-value": val_sel,
1062 "cl-ctrl-core-all-value": val_all,
1064 elif trigger_id == "cl-ctrl-core-all":
1065 val_sel, val_all = self._sync_checklists(
1066 opt = ctrl_panel.get("cl-ctrl-core-options"),
1072 "cl-ctrl-core-value": val_sel,
1073 "cl-ctrl-core-all-value": val_all,
1075 elif trigger_id == "cl-ctrl-framesize":
1076 val_sel, val_all = self._sync_checklists(
1077 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1083 "cl-ctrl-framesize-value": val_sel,
1084 "cl-ctrl-framesize-all-value": val_all,
1086 elif trigger_id == "cl-ctrl-framesize-all":
1087 val_sel, val_all = self._sync_checklists(
1088 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1090 all=cl_framesize_all,
1094 "cl-ctrl-framesize-value": val_sel,
1095 "cl-ctrl-framesize-all-value": val_all,
1097 elif trigger_id == "cl-ctrl-testtype":
1098 val_sel, val_all = self._sync_checklists(
1099 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1105 "cl-ctrl-testtype-value": val_sel,
1106 "cl-ctrl-testtype-all-value": val_all,
1108 elif trigger_id == "cl-ctrl-testtype-all":
1109 val_sel, val_all = self._sync_checklists(
1110 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1112 all=cl_testtype_all,
1116 "cl-ctrl-testtype-value": val_sel,
1117 "cl-ctrl-testtype-all-value": val_all,
1119 elif trigger_id == "btn-ctrl-add":
1121 dut = ctrl_panel.get("dd-ctrl-dut-value")
1122 phy = ctrl_panel.get("dd-ctrl-phy-value")
1123 area = ctrl_panel.get("dd-ctrl-area-value")
1124 test = ctrl_panel.get("dd-ctrl-test-value")
1125 cores = ctrl_panel.get("cl-ctrl-core-value")
1126 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1127 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1128 # Add selected test to the list of tests in store:
1129 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1130 if store_sel is None:
1133 for framesize in framesizes:
1134 for ttype in testtypes:
1138 dut, phy.replace('af_xdp', 'af-xdp'), area,
1139 framesize.lower(), core.lower(), test,
1142 if tid not in [itm["id"] for itm in store_sel]:
1149 "framesize": framesize.lower(),
1150 "core": core.lower(),
1151 "testtype": ttype.lower()
1153 store_sel = sorted(store_sel, key=lambda d: d["id"])
1154 row_card_sel_tests = self.STYLE_ENABLED
1155 row_btns_sel_tests = self.STYLE_ENABLED
1156 ctrl_panel.set(ctrl_panel.defaults)
1158 "cl-selected-options": self._list_tests(store_sel)
1160 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1161 _generate_plotting_area(
1163 self.data, store_sel, self.layout, d_start,
1166 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1168 elif trigger_id == "dpr-period":
1169 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1170 _generate_plotting_area(
1172 self.data, store_sel, self.layout, d_start, d_end
1174 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1176 elif trigger_id == "btn-sel-remove-all":
1178 row_fig_tput = self.PLACEHOLDER
1179 row_fig_lat = self.PLACEHOLDER
1180 row_btn_dwnld = self.PLACEHOLDER
1181 row_card_sel_tests = self.STYLE_DISABLED
1182 row_btns_sel_tests = self.STYLE_DISABLED
1185 "cl-selected-options": list()
1187 elif trigger_id == "btn-sel-remove":
1190 new_store_sel = list()
1191 for item in store_sel:
1192 if item["id"] not in list_sel:
1193 new_store_sel.append(item)
1194 store_sel = new_store_sel
1196 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1197 _generate_plotting_area(
1199 self.data, store_sel, self.layout, d_start,
1202 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1205 "cl-selected-options": self._list_tests(store_sel)
1208 row_fig_tput = self.PLACEHOLDER
1209 row_fig_lat = self.PLACEHOLDER
1210 row_btn_dwnld = self.PLACEHOLDER
1211 row_card_sel_tests = self.STYLE_DISABLED
1212 row_btns_sel_tests = self.STYLE_DISABLED
1215 "cl-selected-options": list()
1217 elif trigger_id == "url":
1218 # TODO: Add verification
1219 url_params = parsed_url["params"]
1221 store_sel = literal_eval(
1222 url_params.get("store_sel", list())[0])
1223 d_start = self._get_date(url_params.get("start", list())[0])
1224 d_end = self._get_date(url_params.get("end", list())[0])
1226 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1227 _generate_plotting_area(
1229 self.data, store_sel, self.layout, d_start,
1233 parsed_url, store_sel, d_start, d_end
1236 row_card_sel_tests = self.STYLE_ENABLED
1237 row_btns_sel_tests = self.STYLE_ENABLED
1239 "cl-selected-options": self._list_tests(store_sel)
1242 row_fig_tput = self.PLACEHOLDER
1243 row_fig_lat = self.PLACEHOLDER
1244 row_btn_dwnld = self.PLACEHOLDER
1245 row_card_sel_tests = self.STYLE_DISABLED
1246 row_btns_sel_tests = self.STYLE_DISABLED
1249 "cl-selected-options": list()
1252 if ctrl_panel.get("cl-ctrl-core-value") and \
1253 ctrl_panel.get("cl-ctrl-framesize-value") and \
1254 ctrl_panel.get("cl-ctrl-testtype-value"):
1259 "btn-ctrl-add-disabled": disabled
1263 ctrl_panel.panel, store_sel,
1264 row_fig_tput, row_fig_lat, row_btn_dwnld,
1265 row_card_sel_tests, row_btns_sel_tests
1267 ret_val.extend(ctrl_panel.values())
1271 Output("metadata-tput-lat", "children"),
1272 Output("metadata-hdrh-graph", "children"),
1273 Output("offcanvas-metadata", "is_open"),
1274 Input({"type": "graph", "index": ALL}, "clickData"),
1275 prevent_initial_call=True
1277 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1282 callback_context.triggered[0]["prop_id"].split(".")[0]
1284 idx = 0 if trigger_id == "tput" else 1
1285 graph_data = graph_data[idx]["points"][0]
1286 except (JSONDecodeError, IndexError, KeyError, ValueError,
1290 metadata = no_update
1295 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1296 ) for x in graph_data.get("text", "").split("<br>")
1298 if trigger_id == "tput":
1299 title = "Throughput"
1300 elif trigger_id == "lat":
1302 hdrh_data = graph_data.get("customdata", None)
1305 class_name="gy-2 p-0",
1307 dbc.CardHeader(hdrh_data.pop("name")),
1308 dbc.CardBody(children=[
1310 id="hdrh-latency-graph",
1311 figure=graph_hdrh_latency(
1312 hdrh_data, self.layout
1320 class_name="gy-2 p-0",
1322 dbc.CardHeader(children=[
1324 target_id="tput-lat-metadata",
1326 style={"display": "inline-block"}
1331 id="tput-lat-metadata",
1333 children=[dbc.ListGroup(children, flush=True), ]
1339 return metadata, graph, True
1342 Output("download-data", "data"),
1343 State("selected-tests", "data"),
1344 Input("btn-download-data", "n_clicks"),
1345 prevent_initial_call=True
1347 def _download_data(store_sel, n_clicks):
1358 for itm in store_sel:
1359 sel_data = select_trending_data(self.data, itm)
1360 if sel_data is None:
1362 df = pd.concat([df, sel_data], ignore_index=True)
1364 return dcc.send_data_frame(df.to_csv, "trending_data.csv")