1 # Copyright (c) 2023 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.
20 import dash_bootstrap_components as dbc
22 from flask import Flask
25 from dash import callback_context, no_update, ALL
26 from dash import Input, Output, State
27 from dash.exceptions import PreventUpdate
28 from yaml import load, FullLoader, YAMLError
29 from ast import literal_eval
31 from ..utils.constants import Constants as C
32 from ..utils.control_panel import ControlPanel
33 from ..utils.trigger import Trigger
34 from ..utils.utils import show_tooltip, label, sync_checklists, gen_new_url, \
35 generate_options, get_list_group_items, graph_hdrh_latency
36 from ..utils.url_processing import url_decode
37 from .graphs import graph_iterative, select_iterative_data
40 # Control panel partameters and their default values.
46 "dd-dutver-opt": list(),
47 "dd-dutver-dis": True,
48 "dd-dutver-val": str(),
52 "dd-area-opt": list(),
55 "dd-test-opt": list(),
58 "cl-core-opt": list(),
59 "cl-core-val": list(),
60 "cl-core-all-val": list(),
61 "cl-core-all-opt": C.CL_ALL_DISABLED,
62 "cl-frmsize-opt": list(),
63 "cl-frmsize-val": list(),
64 "cl-frmsize-all-val": list(),
65 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
66 "cl-tsttype-opt": list(),
67 "cl-tsttype-val": list(),
68 "cl-tsttype-all-val": list(),
69 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
71 "cl-normalize-val": list()
76 """The layout of the dash app and the callbacks.
82 data_iterative: pd.DataFrame,
83 html_layout_file: str,
84 graph_layout_file: str,
88 - save the input parameters,
89 - read and pre-process the data,
90 - prepare data for the control panel,
91 - read HTML layout file,
92 - read tooltips from the tooltip file.
94 :param app: Flask application running the dash application.
95 :param html_layout_file: Path and name of the file specifying the HTML
96 layout of the dash application.
97 :param graph_layout_file: Path and name of the file with layout of
99 :param tooltip_file: Path and name of the yaml file specifying the
102 :type html_layout_file: str
103 :type graph_layout_file: str
104 :type tooltip_file: str
109 self._html_layout_file = html_layout_file
110 self._graph_layout_file = graph_layout_file
111 self._tooltip_file = tooltip_file
112 self._data = data_iterative
114 # Get structure of tests:
117 "job", "test_id", "test_type", "dut_version", "tg_type", "release"
119 for _, row in self._data[cols].drop_duplicates().iterrows():
121 lst_job = row["job"].split("-")
123 d_ver = row["dut_version"]
124 tbed = "-".join(lst_job[-2:])
125 lst_test_id = row["test_id"].split(".")
129 area = ".".join(lst_test_id[3:-2])
130 suite = lst_test_id[-2].replace("2n1l-", "").replace("1n1l-", "").\
132 test = lst_test_id[-1]
133 nic = suite.split("-")[0]
134 for drv in C.DRIVERS:
136 driver = drv.replace("-", "_")
137 test = test.replace(f"{drv}-", "")
141 infra = "-".join((tbed, nic, driver))
142 lst_test = test.split("-")
143 framesize = lst_test[0]
144 core = lst_test[1] if lst_test[1] else "8C"
145 test = "-".join(lst_test[2: -1])
147 if tbs.get(rls, None) is None:
149 if tbs[rls].get(dut, None) is None:
150 tbs[rls][dut] = dict()
151 if tbs[rls][dut].get(d_ver, None) is None:
152 tbs[rls][dut][d_ver] = dict()
153 if tbs[rls][dut][d_ver].get(area, None) is None:
154 tbs[rls][dut][d_ver][area] = dict()
155 if tbs[rls][dut][d_ver][area].get(test, None) is None:
156 tbs[rls][dut][d_ver][area][test] = dict()
157 if tbs[rls][dut][d_ver][area][test].get(infra, None) is None:
158 tbs[rls][dut][d_ver][area][test][infra] = {
160 "frame-size": list(),
163 tst_params = tbs[rls][dut][d_ver][area][test][infra]
164 if core.upper() not in tst_params["core"]:
165 tst_params["core"].append(core.upper())
166 if framesize.upper() not in tst_params["frame-size"]:
167 tst_params["frame-size"].append(framesize.upper())
168 if row["test_type"] == "mrr":
169 if "MRR" not in tst_params["test-type"]:
170 tst_params["test-type"].append("MRR")
171 elif row["test_type"] == "ndrpdr":
172 if "NDR" not in tst_params["test-type"]:
173 tst_params["test-type"].extend(("NDR", "PDR", ))
174 elif row["test_type"] == "hoststack" and \
175 row["tg_type"] in ("iperf", "vpp"):
176 if "BPS" not in tst_params["test-type"]:
177 tst_params["test-type"].append("BPS")
178 elif row["test_type"] == "hoststack" and row["tg_type"] == "ab":
179 if "CPS" not in tst_params["test-type"]:
180 tst_params["test-type"].extend(("CPS", "RPS"))
184 self._html_layout = str()
185 self._graph_layout = None
186 self._tooltips = dict()
189 with open(self._html_layout_file, "r") as file_read:
190 self._html_layout = file_read.read()
191 except IOError as err:
193 f"Not possible to open the file {self._html_layout_file}\n{err}"
197 with open(self._graph_layout_file, "r") as file_read:
198 self._graph_layout = load(file_read, Loader=FullLoader)
199 except IOError as err:
201 f"Not possible to open the file {self._graph_layout_file}\n"
204 except YAMLError as err:
206 f"An error occurred while parsing the specification file "
207 f"{self._graph_layout_file}\n{err}"
211 with open(self._tooltip_file, "r") as file_read:
212 self._tooltips = load(file_read, Loader=FullLoader)
213 except IOError as err:
215 f"Not possible to open the file {self._tooltip_file}\n{err}"
217 except YAMLError as err:
219 f"An error occurred while parsing the specification file "
220 f"{self._tooltip_file}\n{err}"
224 if self._app is not None and hasattr(self, "callbacks"):
225 self.callbacks(self._app)
228 def html_layout(self):
229 return self._html_layout
231 def add_content(self):
232 """Top level method which generated the web page.
235 - Store for user input data,
237 - Main area with control panel and ploting area.
239 If no HTML layout is provided, an error message is displayed instead.
241 :returns: The HTML div with the whole page.
245 if self.html_layout and self._spec_tbs:
261 dcc.Store(id="store-selected-tests"),
262 dcc.Store(id="store-control-panel"),
263 dcc.Location(id="url", refresh=False),
264 self._add_ctrl_col(),
265 self._add_plotting_col()
271 id="offcanvas-metadata",
272 title="Throughput And Latency",
276 dbc.Row(id="metadata-tput-lat"),
277 dbc.Row(id="metadata-hdrh-graph")
280 delay_show=C.SPINNER_DELAY
284 id="offcanvas-documentation",
285 title="Documentation",
288 children=html.Iframe(
289 src=C.URL_DOC_REL_NOTES,
309 def _add_navbar(self):
310 """Add nav element with navigation panel. It is placed on the top.
312 :returns: Navigation bar.
313 :rtype: dbc.NavbarSimple
315 return dbc.NavbarSimple(
316 id="navbarsimple-main",
318 dbc.NavItem(dbc.NavLink(
324 dbc.NavItem(dbc.NavLink(
329 dbc.NavItem(dbc.NavLink(
334 dbc.NavItem(dbc.NavLink(
336 id="btn-documentation",
341 brand_external_link=True,
346 def _add_ctrl_col(self) -> dbc.Col:
347 """Add column with controls. It is placed on the left side.
349 :returns: Column with the control panel.
354 children=self._add_ctrl_panel(),
355 className="sticky-top"
359 def _add_plotting_col(self) -> dbc.Col:
360 """Add column with plots. It is placed on the right side.
362 :returns: Column with plots.
366 id="col-plotting-area",
372 class_name="g-0 p-0",
383 def _add_ctrl_panel(self) -> list:
384 """Add control panel.
386 :returns: Control panel.
391 class_name="g-0 p-1",
396 children=show_tooltip(
403 id={"type": "ctrl-dd", "index": "rls"},
404 placeholder="Select a Release...",
407 {"label": k, "value": k} \
408 for k in self._spec_tbs.keys()
410 key=lambda d: d["label"]
419 class_name="g-0 p-1",
424 children=show_tooltip(
431 id={"type": "ctrl-dd", "index": "dut"},
432 placeholder="Select a Device under Test..."
440 class_name="g-0 p-1",
445 children=show_tooltip(
452 id={"type": "ctrl-dd", "index": "dutver"},
454 "Select a Version of Device under Test..."
462 class_name="g-0 p-1",
467 children=show_tooltip(
474 id={"type": "ctrl-dd", "index": "area"},
475 placeholder="Select an Area..."
483 class_name="g-0 p-1",
488 children=show_tooltip(
495 id={"type": "ctrl-dd", "index": "test"},
496 placeholder="Select a Test..."
504 class_name="g-0 p-1",
509 children=show_tooltip(
516 id={"type": "ctrl-dd", "index": "phy"},
518 "Select a Physical Test Bed Topology..."
526 class_name="g-0 p-1",
531 children=show_tooltip(
542 "index": "frmsize-all"
544 options=C.CL_ALL_DISABLED,
563 style={"align-items": "center"},
569 class_name="g-0 p-1",
574 children=show_tooltip(
587 options=C.CL_ALL_DISABLED,
606 style={"align-items": "center"},
612 class_name="g-0 p-1",
617 children=show_tooltip(
628 "index": "tsttype-all"
630 options=C.CL_ALL_DISABLED,
649 style={"align-items": "center"},
655 class_name="g-0 p-1",
660 children=show_tooltip(
671 "value": "normalize",
673 "Normalize to CPU frequency "
684 style={"align-items": "center"},
690 class_name="g-0 p-1",
693 id={"type": "ctrl-btn", "index": "add-test"},
694 children="Add Selected",
700 id="row-card-sel-tests",
701 class_name="g-0 p-1",
702 style=C.STYLE_DISABLED,
705 class_name="overflow-auto p-0",
708 style={"max-height": "20em"},
714 id="row-btns-sel-tests",
715 class_name="g-0 p-1",
716 style=C.STYLE_DISABLED,
721 id={"type": "ctrl-btn", "index": "rm-test"},
722 children="Remove Selected",
728 id={"type": "ctrl-btn", "index": "rm-test-all"},
729 children="Remove All",
740 def _get_plotting_area(
746 """Generate the plotting area with all its content.
748 :param tests: List of tests to be displayed in the graphs.
749 :param normalize: If true, the values in graphs are normalized.
750 :param url: URL to be displayed in the modal window.
752 :type normalize: bool
754 :returns: List of rows with elements to be displayed in the plotting
762 graph_iterative(self._data, tests, self._graph_layout, normalize)
770 id={"type": "graph", "index": "tput"},
782 id={"type": "graph", "index": "bandwidth"},
786 tab_id="tab-bandwidth"
794 id={"type": "graph", "index": "lat"},
807 active_tab="tab-tput",
821 "text-transform": "none",
822 "padding": "0rem 1rem"
827 dbc.ModalHeader(dbc.ModalTitle("URL")),
836 id="plot-btn-download",
837 children="Download Data",
841 "text-transform": "none",
842 "padding": "0rem 1rem"
845 dcc.Download(id="download-iterative-data")
848 "d-grid gap-0 d-md-flex justify-content-md-end"
855 def callbacks(self, app):
856 """Callbacks for the whole application.
858 :param app: The application.
864 Output("store-control-panel", "data"),
865 Output("store-selected-tests", "data"),
866 Output("plotting-area", "children"),
867 Output("row-card-sel-tests", "style"),
868 Output("row-btns-sel-tests", "style"),
869 Output("lg-selected", "children"),
871 Output({"type": "ctrl-dd", "index": "rls"}, "value"),
872 Output({"type": "ctrl-dd", "index": "dut"}, "options"),
873 Output({"type": "ctrl-dd", "index": "dut"}, "disabled"),
874 Output({"type": "ctrl-dd", "index": "dut"}, "value"),
875 Output({"type": "ctrl-dd", "index": "dutver"}, "options"),
876 Output({"type": "ctrl-dd", "index": "dutver"}, "disabled"),
877 Output({"type": "ctrl-dd", "index": "dutver"}, "value"),
878 Output({"type": "ctrl-dd", "index": "phy"}, "options"),
879 Output({"type": "ctrl-dd", "index": "phy"}, "disabled"),
880 Output({"type": "ctrl-dd", "index": "phy"}, "value"),
881 Output({"type": "ctrl-dd", "index": "area"}, "options"),
882 Output({"type": "ctrl-dd", "index": "area"}, "disabled"),
883 Output({"type": "ctrl-dd", "index": "area"}, "value"),
884 Output({"type": "ctrl-dd", "index": "test"}, "options"),
885 Output({"type": "ctrl-dd", "index": "test"}, "disabled"),
886 Output({"type": "ctrl-dd", "index": "test"}, "value"),
887 Output({"type": "ctrl-cl", "index": "core"}, "options"),
888 Output({"type": "ctrl-cl", "index": "core"}, "value"),
889 Output({"type": "ctrl-cl", "index": "core-all"}, "value"),
890 Output({"type": "ctrl-cl", "index": "core-all"}, "options"),
891 Output({"type": "ctrl-cl", "index": "frmsize"}, "options"),
892 Output({"type": "ctrl-cl", "index": "frmsize"}, "value"),
893 Output({"type": "ctrl-cl", "index": "frmsize-all"}, "value"),
894 Output({"type": "ctrl-cl", "index": "frmsize-all"}, "options"),
895 Output({"type": "ctrl-cl", "index": "tsttype"}, "options"),
896 Output({"type": "ctrl-cl", "index": "tsttype"}, "value"),
897 Output({"type": "ctrl-cl", "index": "tsttype-all"}, "value"),
898 Output({"type": "ctrl-cl", "index": "tsttype-all"}, "options"),
899 Output({"type": "ctrl-btn", "index": "add-test"}, "disabled"),
900 Output("normalize", "value")
903 State("store-control-panel", "data"),
904 State("store-selected-tests", "data"),
905 State({"type": "sel-cl", "index": ALL}, "value")
908 Input("url", "href"),
909 Input("normalize", "value"),
911 Input({"type": "ctrl-dd", "index": ALL}, "value"),
912 Input({"type": "ctrl-cl", "index": ALL}, "value"),
913 Input({"type": "ctrl-btn", "index": ALL}, "n_clicks")
916 def _update_application(
924 """Update the application when the event is detected.
927 ctrl_panel = ControlPanel(CP_PARAMS, control_panel)
931 parsed_url = url_decode(href)
933 url_params = parsed_url["params"]
937 plotting_area = no_update
938 row_card_sel_tests = no_update
939 row_btns_sel_tests = no_update
940 lg_selected = no_update
942 trigger = Trigger(callback_context.triggered)
944 if trigger.type == "url" and url_params:
946 store_sel = literal_eval(url_params["store_sel"][0])
947 normalize = literal_eval(url_params["norm"][0])
948 except (KeyError, IndexError, AttributeError):
951 row_card_sel_tests = C.STYLE_ENABLED
952 row_btns_sel_tests = C.STYLE_ENABLED
953 last_test = store_sel[-1]
954 test = self._spec_tbs[last_test["rls"]][last_test["dut"]]\
955 [last_test["dutver"]][last_test["area"]]\
956 [last_test["test"]][last_test["phy"]]
958 "dd-rls-val": last_test["rls"],
959 "dd-dut-val": last_test["dut"],
960 "dd-dut-opt": generate_options(
961 self._spec_tbs[last_test["rls"]].keys()
964 "dd-dutver-val": last_test["dutver"],
965 "dd-dutver-opt": generate_options(
966 self._spec_tbs[last_test["rls"]]\
967 [last_test["dut"]].keys()
969 "dd-dutver-dis": False,
970 "dd-area-val": last_test["area"],
972 {"label": label(v), "value": v} for v in \
973 sorted(self._spec_tbs[last_test["rls"]]\
975 [last_test["dutver"]].keys())
977 "dd-area-dis": False,
978 "dd-test-val": last_test["test"],
979 "dd-test-opt": generate_options(
980 self._spec_tbs[last_test["rls"]][last_test["dut"]]\
981 [last_test["dutver"]][last_test["area"]].keys()
983 "dd-test-dis": False,
984 "dd-phy-val": last_test["phy"],
985 "dd-phy-opt": generate_options(
986 self._spec_tbs[last_test["rls"]][last_test["dut"]]\
987 [last_test["dutver"]][last_test["area"]]\
988 [last_test["test"]].keys()
991 "cl-core-opt": generate_options(test["core"]),
992 "cl-core-val": [last_test["core"].upper(), ],
993 "cl-core-all-val": list(),
994 "cl-core-all-opt": C.CL_ALL_ENABLED,
995 "cl-frmsize-opt": generate_options(test["frame-size"]),
996 "cl-frmsize-val": [last_test["framesize"].upper(), ],
997 "cl-frmsize-all-val": list(),
998 "cl-frmsize-all-opt": C.CL_ALL_ENABLED,
999 "cl-tsttype-opt": generate_options(test["test-type"]),
1000 "cl-tsttype-val": [last_test["testtype"].upper(), ],
1001 "cl-tsttype-all-val": list(),
1002 "cl-tsttype-all-opt": C.CL_ALL_ENABLED,
1003 "cl-normalize-val": normalize,
1004 "btn-add-dis": False
1007 elif trigger.type == "normalize":
1008 ctrl_panel.set({"cl-normalize-val": normalize})
1010 elif trigger.type == "ctrl-dd":
1011 if trigger.idx == "rls":
1013 options = generate_options(
1014 self._spec_tbs[trigger.value].keys()
1021 "dd-rls-val": trigger.value,
1022 "dd-dut-val": str(),
1023 "dd-dut-opt": options,
1024 "dd-dut-dis": disabled,
1025 "dd-dutver-val": str(),
1026 "dd-dutver-opt": list(),
1027 "dd-dutver-dis": True,
1028 "dd-phy-val": str(),
1029 "dd-phy-opt": list(),
1031 "dd-area-val": str(),
1032 "dd-area-opt": list(),
1033 "dd-area-dis": True,
1034 "dd-test-val": str(),
1035 "dd-test-opt": list(),
1036 "dd-test-dis": True,
1037 "cl-core-opt": list(),
1038 "cl-core-val": list(),
1039 "cl-core-all-val": list(),
1040 "cl-core-all-opt": C.CL_ALL_DISABLED,
1041 "cl-frmsize-opt": list(),
1042 "cl-frmsize-val": list(),
1043 "cl-frmsize-all-val": list(),
1044 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1045 "cl-tsttype-opt": list(),
1046 "cl-tsttype-val": list(),
1047 "cl-tsttype-all-val": list(),
1048 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1051 elif trigger.idx == "dut":
1053 rls = ctrl_panel.get("dd-rls-val")
1054 dut = self._spec_tbs[rls][trigger.value]
1055 options = generate_options(dut.keys())
1061 "dd-dut-val": trigger.value,
1062 "dd-dutver-val": str(),
1063 "dd-dutver-opt": options,
1064 "dd-dutver-dis": disabled,
1065 "dd-phy-val": str(),
1066 "dd-phy-opt": list(),
1068 "dd-area-val": str(),
1069 "dd-area-opt": list(),
1070 "dd-area-dis": True,
1071 "dd-test-val": str(),
1072 "dd-test-opt": list(),
1073 "dd-test-dis": True,
1074 "cl-core-opt": list(),
1075 "cl-core-val": list(),
1076 "cl-core-all-val": list(),
1077 "cl-core-all-opt": C.CL_ALL_DISABLED,
1078 "cl-frmsize-opt": list(),
1079 "cl-frmsize-val": list(),
1080 "cl-frmsize-all-val": list(),
1081 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1082 "cl-tsttype-opt": list(),
1083 "cl-tsttype-val": list(),
1084 "cl-tsttype-all-val": list(),
1085 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1088 elif trigger.idx == "dutver":
1090 rls = ctrl_panel.get("dd-rls-val")
1091 dut = ctrl_panel.get("dd-dut-val")
1092 dutver = self._spec_tbs[rls][dut][trigger.value]
1093 options = [{"label": label(v), "value": v} \
1094 for v in sorted(dutver.keys())]
1100 "dd-dutver-val": trigger.value,
1101 "dd-area-val": str(),
1102 "dd-area-opt": options,
1103 "dd-area-dis": disabled,
1104 "dd-test-val": str(),
1105 "dd-test-opt": list(),
1106 "dd-test-dis": True,
1107 "dd-phy-val": str(),
1108 "dd-phy-opt": list(),
1110 "cl-core-opt": list(),
1111 "cl-core-val": list(),
1112 "cl-core-all-val": list(),
1113 "cl-core-all-opt": C.CL_ALL_DISABLED,
1114 "cl-frmsize-opt": list(),
1115 "cl-frmsize-val": list(),
1116 "cl-frmsize-all-val": list(),
1117 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1118 "cl-tsttype-opt": list(),
1119 "cl-tsttype-val": list(),
1120 "cl-tsttype-all-val": list(),
1121 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1124 elif trigger.idx == "area":
1126 rls = ctrl_panel.get("dd-rls-val")
1127 dut = ctrl_panel.get("dd-dut-val")
1128 dutver = ctrl_panel.get("dd-dutver-val")
1129 area = self._spec_tbs[rls][dut][dutver][trigger.value]
1130 options = generate_options(area.keys())
1136 "dd-area-val": trigger.value,
1137 "dd-test-val": str(),
1138 "dd-test-opt": options,
1139 "dd-test-dis": disabled,
1140 "dd-phy-val": str(),
1141 "dd-phy-opt": list(),
1143 "cl-core-opt": list(),
1144 "cl-core-val": list(),
1145 "cl-core-all-val": list(),
1146 "cl-core-all-opt": C.CL_ALL_DISABLED,
1147 "cl-frmsize-opt": list(),
1148 "cl-frmsize-val": list(),
1149 "cl-frmsize-all-val": list(),
1150 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1151 "cl-tsttype-opt": list(),
1152 "cl-tsttype-val": list(),
1153 "cl-tsttype-all-val": list(),
1154 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1157 elif trigger.idx == "test":
1159 rls = ctrl_panel.get("dd-rls-val")
1160 dut = ctrl_panel.get("dd-dut-val")
1161 dutver = ctrl_panel.get("dd-dutver-val")
1162 area = ctrl_panel.get("dd-area-val")
1163 test = self._spec_tbs[rls][dut][dutver][area]\
1165 options = generate_options(test.keys())
1171 "dd-test-val": trigger.value,
1172 "dd-phy-val": str(),
1173 "dd-phy-opt": options,
1174 "dd-phy-dis": disabled,
1175 "cl-core-opt": list(),
1176 "cl-core-val": list(),
1177 "cl-core-all-val": list(),
1178 "cl-core-all-opt": C.CL_ALL_DISABLED,
1179 "cl-frmsize-opt": list(),
1180 "cl-frmsize-val": list(),
1181 "cl-frmsize-all-val": list(),
1182 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1183 "cl-tsttype-opt": list(),
1184 "cl-tsttype-val": list(),
1185 "cl-tsttype-all-val": list(),
1186 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1189 elif trigger.idx == "phy":
1190 rls = ctrl_panel.get("dd-rls-val")
1191 dut = ctrl_panel.get("dd-dut-val")
1192 dutver = ctrl_panel.get("dd-dutver-val")
1193 area = ctrl_panel.get("dd-area-val")
1194 test = ctrl_panel.get("dd-test-val")
1195 if all((rls, dut, dutver, area, test, trigger.value, )):
1196 phy = self._spec_tbs[rls][dut][dutver][area][test]\
1199 "dd-phy-val": trigger.value,
1200 "cl-core-opt": generate_options(phy["core"]),
1201 "cl-core-val": list(),
1202 "cl-core-all-val": list(),
1203 "cl-core-all-opt": C.CL_ALL_ENABLED,
1205 generate_options(phy["frame-size"]),
1206 "cl-frmsize-val": list(),
1207 "cl-frmsize-all-val": list(),
1208 "cl-frmsize-all-opt": C.CL_ALL_ENABLED,
1210 generate_options(phy["test-type"]),
1211 "cl-tsttype-val": list(),
1212 "cl-tsttype-all-val": list(),
1213 "cl-tsttype-all-opt": C.CL_ALL_ENABLED,
1216 elif trigger.type == "ctrl-cl":
1217 param = trigger.idx.split("-")[0]
1218 if "-all" in trigger.idx:
1219 c_sel, c_all, c_id = list(), trigger.value, "all"
1221 c_sel, c_all, c_id = trigger.value, list(), str()
1222 val_sel, val_all = sync_checklists(
1223 options=ctrl_panel.get(f"cl-{param}-opt"),
1229 f"cl-{param}-val": val_sel,
1230 f"cl-{param}-all-val": val_all,
1232 if all((ctrl_panel.get("cl-core-val"),
1233 ctrl_panel.get("cl-frmsize-val"),
1234 ctrl_panel.get("cl-tsttype-val"), )):
1235 ctrl_panel.set({"btn-add-dis": False})
1237 ctrl_panel.set({"btn-add-dis": True})
1238 elif trigger.type == "ctrl-btn":
1240 if trigger.idx == "add-test":
1241 rls = ctrl_panel.get("dd-rls-val")
1242 dut = ctrl_panel.get("dd-dut-val")
1243 dutver = ctrl_panel.get("dd-dutver-val")
1244 phy = ctrl_panel.get("dd-phy-val")
1245 area = ctrl_panel.get("dd-area-val")
1246 test = ctrl_panel.get("dd-test-val")
1247 # Add selected test to the list of tests in store:
1248 if store_sel is None:
1250 for core in ctrl_panel.get("cl-core-val"):
1251 for framesize in ctrl_panel.get("cl-frmsize-val"):
1252 for ttype in ctrl_panel.get("cl-tsttype-val"):
1259 phy.replace("af_xdp", "af-xdp"),
1266 if tid not in [i["id"] for i in store_sel]:
1275 "framesize": framesize.lower(),
1276 "core": core.lower(),
1277 "testtype": ttype.lower()
1279 store_sel = sorted(store_sel, key=lambda d: d["id"])
1280 if C.CLEAR_ALL_INPUTS:
1281 ctrl_panel.set(ctrl_panel.defaults)
1282 elif trigger.idx == "rm-test" and lst_sel:
1283 new_store_sel = list()
1284 for idx, item in enumerate(store_sel):
1285 if not lst_sel[idx]:
1286 new_store_sel.append(item)
1287 store_sel = new_store_sel
1288 elif trigger.idx == "rm-test-all":
1293 lg_selected = get_list_group_items(
1294 store_sel, "sel-cl", add_index=True
1296 plotting_area = self._get_plotting_area(
1301 {"store_sel": store_sel, "norm": normalize}
1304 row_card_sel_tests = C.STYLE_ENABLED
1305 row_btns_sel_tests = C.STYLE_ENABLED
1307 plotting_area = C.PLACEHOLDER
1308 row_card_sel_tests = C.STYLE_DISABLED
1309 row_btns_sel_tests = C.STYLE_DISABLED
1320 ret_val.extend(ctrl_panel.values)
1324 Output("plot-mod-url", "is_open"),
1325 [Input("plot-btn-url", "n_clicks")],
1326 [State("plot-mod-url", "is_open")],
1328 def toggle_plot_mod_url(n, is_open):
1329 """Toggle the modal window with url.
1336 Output("download-iterative-data", "data"),
1337 State("store-selected-tests", "data"),
1338 Input("plot-btn-download", "n_clicks"),
1339 prevent_initial_call=True
1341 def _download_iterative_data(store_sel, _):
1342 """Download the data
1344 :param store_sel: List of tests selected by user stored in the
1346 :type store_sel: list
1347 :returns: dict of data frame content (base64 encoded) and meta data
1348 used by the Download component.
1356 for itm in store_sel:
1357 sel_data = select_iterative_data(self._data, itm)
1358 if sel_data is None:
1360 df = pd.concat([df, sel_data], ignore_index=True)
1362 return dcc.send_data_frame(df.to_csv, C.REPORT_DOWNLOAD_FILE_NAME)
1365 Output("metadata-tput-lat", "children"),
1366 Output("metadata-hdrh-graph", "children"),
1367 Output("offcanvas-metadata", "is_open"),
1368 Input({"type": "graph", "index": ALL}, "clickData"),
1369 prevent_initial_call=True
1371 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1372 """Generates the data for the offcanvas displayed when a particular
1373 point in a graph is clicked on.
1375 :param graph_data: The data from the clicked point in the graph.
1376 :type graph_data: dict
1377 :returns: The data to be displayed on the offcanvas and the
1378 information to show the offcanvas.
1379 :rtype: tuple(list, list, bool)
1382 trigger = Trigger(callback_context.triggered)
1384 if trigger.idx == "tput":
1386 elif trigger.idx == "bandwidth":
1388 elif trigger.idx == "lat":
1389 idx = len(graph_data) - 1
1391 return list(), list(), False
1394 graph_data = graph_data[idx]["points"]
1395 except (IndexError, KeyError, ValueError, TypeError):
1398 def _process_stats(data: list, param: str) -> list:
1399 """Process statistical data provided by plot.ly box graph.
1401 :param data: Statistical data provided by plot.ly box graph.
1402 :param param: Parameter saying if the data come from "tput" or
1406 :returns: Listo of tuples where the first value is the
1407 statistic's name and the secont one it's value.
1411 stats = ("max", "upper fence", "q3", "median", "q1",
1412 "lower fence", "min")
1413 elif len(data) == 9:
1414 stats = ("outlier", "max", "upper fence", "q3", "median",
1415 "q1", "lower fence", "min", "outlier")
1416 elif len(data) == 1:
1418 stats = ("average latency at 50% PDR", )
1419 elif param == "bandwidth":
1420 stats = ("bandwidth", )
1422 stats = ("throughput", )
1425 unit = " [us]" if param == "lat" else str()
1426 return [(f"{stat}{unit}", f"{value['y']:,.0f}")
1427 for stat, value in zip(stats, data)]
1429 customdata = graph_data[0].get("customdata", dict())
1430 datapoint = customdata.get("metadata", dict())
1431 hdrh_data = customdata.get("hdrh", dict())
1433 list_group_items = list()
1434 for k, v in datapoint.items():
1436 if len(graph_data) > 1:
1438 list_group_item = dbc.ListGroupItem([
1440 html.A(v, href=f"{C.URL_JENKINS}{v}", target="_blank")
1443 list_group_item = dbc.ListGroupItem([dbc.Badge(k), v])
1444 list_group_items.append(list_group_item)
1447 if trigger.idx == "tput":
1448 title = "Throughput"
1449 elif trigger.idx == "bandwidth":
1451 elif trigger.idx == "lat":
1453 if len(graph_data) == 1:
1456 class_name="gy-2 p-0",
1458 dbc.CardHeader(hdrh_data.pop("name")),
1459 dbc.CardBody(dcc.Graph(
1460 id="hdrh-latency-graph",
1461 figure=graph_hdrh_latency(
1462 hdrh_data, self._graph_layout
1470 for k, v in _process_stats(graph_data, trigger.idx):
1471 list_group_items.append(dbc.ListGroupItem([dbc.Badge(k), v]))
1475 class_name="gy-2 p-0",
1477 dbc.CardHeader(children=[
1479 target_id="tput-lat-metadata",
1481 style={"display": "inline-block"}
1486 dbc.ListGroup(list_group_items, flush=True),
1487 id="tput-lat-metadata",
1494 return metadata, graph, True
1497 Output("offcanvas-documentation", "is_open"),
1498 Input("btn-documentation", "n_clicks"),
1499 State("offcanvas-documentation", "is_open")
1501 def toggle_offcanvas_documentation(n_clicks, is_open):