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 ..data.url_processing import url_decode, url_encode
35 from .graphs import graph_trending, graph_hdrh_latency, \
43 # If True, clear all inputs in control panel when button "ADD SELECTED" is
45 CLEAR_ALL_INPUTS = False
47 STYLE_DISABLED = {"display": "none"}
48 STYLE_ENABLED = {"display": "inherit"}
61 PLACEHOLDER = html.Nobr("")
63 DRIVERS = ("avf", "af-xdp", "rdma", "dpdk")
67 "container_memif": "LXC/DRC Container Memif",
68 "crypto": "IPSec IPv4 Routing",
69 "ip4": "IPv4 Routing",
70 "ip6": "IPv6 Routing",
71 "ip4_tunnels": "IPv4 Tunnels",
72 "l2": "L2 Ethernet Switching",
73 "srv6": "SRv6 Routing",
74 "vm_vhost": "VMs vhost-user",
75 "nfv_density-dcr_memif-chain_ipsec": "CNF Service Chains Routing IPSec",
76 "nfv_density-vm_vhost-chain_dot1qip4vxlan":"VNF Service Chains Tunnels",
77 "nfv_density-vm_vhost-chain": "VNF Service Chains Routing",
78 "nfv_density-dcr_memif-pipeline": "CNF Service Pipelines Routing",
79 "nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
83 "background-color": "#d2ebf5",
84 "border-color": "#bce1f1",
88 def __init__(self, app: Flask, html_layout_file: str,
89 graph_layout_file: str, data_spec_file: str, tooltip_file: str,
90 time_period: str=None) -> None:
96 self._html_layout_file = html_layout_file
97 self._graph_layout_file = graph_layout_file
98 self._data_spec_file = data_spec_file
99 self._tooltip_file = tooltip_file
100 self._time_period = time_period
104 data_spec_file=self._data_spec_file,
106 ).read_trending_mrr(days=self._time_period)
109 data_spec_file=self._data_spec_file,
111 ).read_trending_ndrpdr(days=self._time_period)
113 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
116 (datetime.utcnow() - self._data["start_time"].min()).days
117 if self._time_period > data_time_period:
118 self._time_period = data_time_period
121 # Get structure of tests:
123 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
125 lst_job = row["job"].split("-")
128 tbed = "-".join(lst_job[-2:])
129 lst_test = row["test_id"].split(".")
133 area = "-".join(lst_test[3:-2])
134 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
137 nic = suite.split("-")[0]
138 for drv in self.DRIVERS:
144 test = test.replace(f"{drv}-", "")
148 infra = "-".join((tbed, nic, driver))
149 lst_test = test.split("-")
150 framesize = lst_test[0]
151 core = lst_test[1] if lst_test[1] else "8C"
152 test = "-".join(lst_test[2: -1])
154 if tbs.get(dut, None) is None:
156 if tbs[dut].get(infra, None) is None:
157 tbs[dut][infra] = dict()
158 if tbs[dut][infra].get(area, None) is None:
159 tbs[dut][infra][area] = dict()
160 if tbs[dut][infra][area].get(test, None) is None:
161 tbs[dut][infra][area][test] = dict()
162 tbs[dut][infra][area][test]["core"] = list()
163 tbs[dut][infra][area][test]["frame-size"] = list()
164 tbs[dut][infra][area][test]["test-type"] = list()
165 if core.upper() not in tbs[dut][infra][area][test]["core"]:
166 tbs[dut][infra][area][test]["core"].append(core.upper())
167 if framesize.upper() not in \
168 tbs[dut][infra][area][test]["frame-size"]:
169 tbs[dut][infra][area][test]["frame-size"].append(
172 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
173 tbs[dut][infra][area][test]["test-type"].append("MRR")
174 elif ttype == "ndrpdr":
175 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
176 tbs[dut][infra][area][test]["test-type"].extend(
181 self._html_layout = ""
182 self._graph_layout = None
183 self._tooltips = dict()
186 with open(self._html_layout_file, "r") as file_read:
187 self._html_layout = file_read.read()
188 except IOError as err:
190 f"Not possible to open the file {self._html_layout_file}\n{err}"
194 with open(self._graph_layout_file, "r") as file_read:
195 self._graph_layout = load(file_read, Loader=FullLoader)
196 except IOError as err:
198 f"Not possible to open the file {self._graph_layout_file}\n"
201 except YAMLError as err:
203 f"An error occurred while parsing the specification file "
204 f"{self._graph_layout_file}\n{err}"
208 with open(self._tooltip_file, "r") as file_read:
209 self._tooltips = load(file_read, Loader=FullLoader)
210 except IOError as err:
212 f"Not possible to open the file {self._tooltip_file}\n{err}"
214 except YAMLError as err:
216 f"An error occurred while parsing the specification file "
217 f"{self._tooltip_file}\n{err}"
221 if self._app is not None and hasattr(self, 'callbacks'):
222 self.callbacks(self._app)
225 def html_layout(self):
226 return self._html_layout
230 return self._spec_tbs
238 return self._graph_layout
241 def time_period(self):
242 return self._time_period
244 def label(self, key: str) -> str:
245 return self.LABELS.get(key, key)
247 def _show_tooltip(self, id: str, title: str,
248 clipboard_id: str=None) -> list:
252 dcc.Clipboard(target_id=clipboard_id, title="Copy URL") \
253 if clipboard_id else str(),
261 class_name="border ms-1",
264 children=self._tooltips.get(id, str()),
270 def add_content(self):
273 if self.html_layout and self.spec_tbs:
287 id="offcanvas-metadata",
288 title="Throughput And Latency",
292 dbc.Row(id="metadata-tput-lat"),
293 dbc.Row(id="metadata-hdrh-graph"),
301 dcc.Store(id="selected-tests"),
302 dcc.Store(id="control-panel"),
303 dcc.Location(id="url", refresh=False),
304 self._add_ctrl_col(),
305 self._add_plotting_col(),
323 def _add_navbar(self):
324 """Add nav element with navigation panel. It is placed on the top.
326 return dbc.NavbarSimple(
327 id="navbarsimple-main",
331 "Continuous Performance Trending",
340 brand_external_link=True,
345 def _add_ctrl_col(self) -> dbc.Col:
346 """Add column with controls. It is placed on the left side.
351 self._add_ctrl_panel(),
355 def _add_plotting_col(self) -> dbc.Col:
356 """Add column with plots and tables. It is placed on the right side.
359 id="col-plotting-area",
363 dbc.Row( # Throughput
365 class_name="g-0 p-2",
372 class_name="g-0 p-2",
378 id="row-btn-download",
379 class_name="g-0 p-2",
390 def _add_ctrl_panel(self) -> dbc.Row:
395 class_name="g-0 p-2",
403 children=self._show_tooltip(
409 "Select a Device under Test..."
413 {"label": k, "value": k} \
414 for k in self.spec_tbs.keys()
416 key=lambda d: d["label"]
431 children=self._show_tooltip(
432 "help-infra", "Infra")
437 "Select a Physical Test Bed "
453 children=self._show_tooltip(
458 placeholder="Select an Area...",
473 children=self._show_tooltip(
478 placeholder="Select a Test...",
488 id="row-ctrl-framesize",
492 children=self._show_tooltip(
493 "help-framesize", "Frame Size"),
499 id="cl-ctrl-framesize-all",
500 options=self.CL_ALL_DISABLED,
510 id="cl-ctrl-framesize",
523 children=self._show_tooltip(
524 "help-cores", "Number of Cores"),
530 id="cl-ctrl-core-all",
531 options=self.CL_ALL_DISABLED,
550 id="row-ctrl-testtype",
554 children=self._show_tooltip(
555 "help-ttype", "Test Type"),
561 id="cl-ctrl-testtype-all",
562 options=self.CL_ALL_DISABLED,
572 id="cl-ctrl-testtype",
581 id="row-ctrl-normalize",
585 children=self._show_tooltip(
586 "help-normalize", "Normalize"),
592 id="cl-ctrl-normalize",
594 "value": "normalize",
596 "Normalize results to CPU"
609 class_name="gy-1 p-0",
615 children="Add Selected",
629 children=self._show_tooltip(
630 "help-time-period", "Time Period"),
634 className="d-flex justify-content-center",
636 datetime.utcnow() - timedelta(
637 days=self.time_period),
638 max_date_allowed=datetime.utcnow(),
639 initial_visible_month=datetime.utcnow(),
641 datetime.utcnow() - timedelta(
642 days=self.time_period),
643 end_date=datetime.utcnow(),
644 display_format="D MMM YY"
649 id="row-card-sel-tests",
651 style=self.STYLE_DISABLED,
658 class_name="overflow-auto",
662 style={"max-height": "12em"},
667 id="row-btns-sel-tests",
668 style=self.STYLE_DISABLED,
675 children="Remove Selected",
676 class_name="w-100 me-1",
681 id="btn-sel-remove-all",
682 children="Remove All",
683 class_name="w-100 me-1",
696 def __init__(self, panel: dict) -> None:
704 # Defines also the order of keys
706 "dd-ctrl-dut-value": str(),
707 "dd-ctrl-phy-options": list(),
708 "dd-ctrl-phy-disabled": True,
709 "dd-ctrl-phy-value": str(),
710 "dd-ctrl-area-options": list(),
711 "dd-ctrl-area-disabled": True,
712 "dd-ctrl-area-value": str(),
713 "dd-ctrl-test-options": list(),
714 "dd-ctrl-test-disabled": True,
715 "dd-ctrl-test-value": str(),
716 "cl-ctrl-core-options": list(),
717 "cl-ctrl-core-value": list(),
718 "cl-ctrl-core-all-value": list(),
719 "cl-ctrl-core-all-options": CL_ALL_DISABLED,
720 "cl-ctrl-framesize-options": list(),
721 "cl-ctrl-framesize-value": list(),
722 "cl-ctrl-framesize-all-value": list(),
723 "cl-ctrl-framesize-all-options": CL_ALL_DISABLED,
724 "cl-ctrl-testtype-options": list(),
725 "cl-ctrl-testtype-value": list(),
726 "cl-ctrl-testtype-all-value": list(),
727 "cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
728 "btn-ctrl-add-disabled": True,
729 "cl-normalize-value": list(),
730 "cl-selected-options": list(),
733 self._panel = deepcopy(self._defaults)
735 for key in self._defaults:
736 self._panel[key] = panel[key]
739 def defaults(self) -> dict:
740 return self._defaults
743 def panel(self) -> dict:
746 def set(self, kwargs: dict) -> None:
747 for key, val in kwargs.items():
748 if key in self._panel:
749 self._panel[key] = val
751 raise KeyError(f"The key {key} is not defined.")
753 def get(self, key: str) -> any:
754 return self._panel[key]
756 def values(self) -> tuple:
757 return tuple(self._panel.values())
760 def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
763 options = {v["value"] for v in opt}
765 sel = list(options) if all else list()
767 all = ["all", ] if set(sel) == options else list()
771 def _list_tests(selection: dict) -> list:
772 """Display selected tests with checkboxes
775 return [{"label": v["id"], "value": v["id"]} for v in selection]
780 def _get_date(s_date: str) -> datetime:
781 return datetime(int(s_date[0:4]), int(s_date[5:7]), int(s_date[8:10]))
783 def callbacks(self, app):
785 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
789 (fig_tput, fig_lat) = figs
791 row_fig_tput = self.PLACEHOLDER
792 row_fig_lat = self.PLACEHOLDER
793 row_btn_dwnld = self.PLACEHOLDER
798 id={"type": "graph", "index": "tput"},
806 dcc.Loading(children=[
808 id="btn-download-data",
809 children=self._show_tooltip(
810 "help-download", "Download Data"),
814 dcc.Download(id="download-data")
825 style=self.URL_STYLE,
826 children=self._show_tooltip(
827 "help-url", "URL", "input-url")
833 style=self.URL_STYLE,
844 id={"type": "graph", "index": "lat"},
849 return row_fig_tput, row_fig_lat, row_btn_dwnld
852 Output("control-panel", "data"), # Store
853 Output("selected-tests", "data"), # Store
854 Output("row-graph-tput", "children"),
855 Output("row-graph-lat", "children"),
856 Output("row-btn-download", "children"),
857 Output("row-card-sel-tests", "style"),
858 Output("row-btns-sel-tests", "style"),
859 Output("dd-ctrl-dut", "value"),
860 Output("dd-ctrl-phy", "options"),
861 Output("dd-ctrl-phy", "disabled"),
862 Output("dd-ctrl-phy", "value"),
863 Output("dd-ctrl-area", "options"),
864 Output("dd-ctrl-area", "disabled"),
865 Output("dd-ctrl-area", "value"),
866 Output("dd-ctrl-test", "options"),
867 Output("dd-ctrl-test", "disabled"),
868 Output("dd-ctrl-test", "value"),
869 Output("cl-ctrl-core", "options"),
870 Output("cl-ctrl-core", "value"),
871 Output("cl-ctrl-core-all", "value"),
872 Output("cl-ctrl-core-all", "options"),
873 Output("cl-ctrl-framesize", "options"),
874 Output("cl-ctrl-framesize", "value"),
875 Output("cl-ctrl-framesize-all", "value"),
876 Output("cl-ctrl-framesize-all", "options"),
877 Output("cl-ctrl-testtype", "options"),
878 Output("cl-ctrl-testtype", "value"),
879 Output("cl-ctrl-testtype-all", "value"),
880 Output("cl-ctrl-testtype-all", "options"),
881 Output("btn-ctrl-add", "disabled"),
882 Output("cl-ctrl-normalize", "value"),
883 Output("cl-selected", "options"), # User selection
884 State("control-panel", "data"), # Store
885 State("selected-tests", "data"), # Store
886 State("cl-selected", "value"), # User selection
887 Input("dd-ctrl-dut", "value"),
888 Input("dd-ctrl-phy", "value"),
889 Input("dd-ctrl-area", "value"),
890 Input("dd-ctrl-test", "value"),
891 Input("cl-ctrl-core", "value"),
892 Input("cl-ctrl-core-all", "value"),
893 Input("cl-ctrl-framesize", "value"),
894 Input("cl-ctrl-framesize-all", "value"),
895 Input("cl-ctrl-testtype", "value"),
896 Input("cl-ctrl-testtype-all", "value"),
897 Input("cl-ctrl-normalize", "value"),
898 Input("btn-ctrl-add", "n_clicks"),
899 Input("dpr-period", "start_date"),
900 Input("dpr-period", "end_date"),
901 Input("btn-sel-remove", "n_clicks"),
902 Input("btn-sel-remove-all", "n_clicks"),
905 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
906 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
907 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
908 cl_testtype: list, cl_testtype_all: list, cl_normalize: list,
909 btn_add: int, d_start: str, d_end: str, btn_remove: int,
910 btn_remove_all: int, href: str) -> tuple:
914 def _gen_new_url(parsed_url: dict, store_sel: list,
915 start: datetime, end: datetime) -> str:
918 new_url = url_encode({
919 "scheme": parsed_url["scheme"],
920 "netloc": parsed_url["netloc"],
921 "path": parsed_url["path"],
923 "store_sel": store_sel,
933 ctrl_panel = self.ControlPanel(cp_data)
935 d_start = self._get_date(d_start)
936 d_end = self._get_date(d_end)
939 parsed_url = url_decode(href)
941 row_fig_tput = no_update
942 row_fig_lat = no_update
943 row_btn_dwnld = no_update
944 row_card_sel_tests = no_update
945 row_btns_sel_tests = no_update
947 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
949 if trigger_id == "dd-ctrl-dut":
951 dut = self.spec_tbs[dd_dut]
953 [{"label": v, "value": v}for v in dut.keys()],
954 key=lambda d: d["label"]
961 "dd-ctrl-dut-value": dd_dut,
962 "dd-ctrl-phy-value": str(),
963 "dd-ctrl-phy-options": options,
964 "dd-ctrl-phy-disabled": disabled,
965 "dd-ctrl-area-value": str(),
966 "dd-ctrl-area-options": list(),
967 "dd-ctrl-area-disabled": True,
968 "dd-ctrl-test-value": str(),
969 "dd-ctrl-test-options": list(),
970 "dd-ctrl-test-disabled": True,
971 "cl-ctrl-core-options": list(),
972 "cl-ctrl-core-value": list(),
973 "cl-ctrl-core-all-value": list(),
974 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
975 "cl-ctrl-framesize-options": list(),
976 "cl-ctrl-framesize-value": list(),
977 "cl-ctrl-framesize-all-value": list(),
978 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
979 "cl-ctrl-testtype-options": list(),
980 "cl-ctrl-testtype-value": list(),
981 "cl-ctrl-testtype-all-value": list(),
982 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
984 elif trigger_id == "dd-ctrl-phy":
986 dut = ctrl_panel.get("dd-ctrl-dut-value")
987 phy = self.spec_tbs[dut][dd_phy]
989 [{"label": self.label(v), "value": v}
990 for v in phy.keys()],
991 key=lambda d: d["label"]
998 "dd-ctrl-phy-value": dd_phy,
999 "dd-ctrl-area-value": str(),
1000 "dd-ctrl-area-options": options,
1001 "dd-ctrl-area-disabled": disabled,
1002 "dd-ctrl-test-value": str(),
1003 "dd-ctrl-test-options": list(),
1004 "dd-ctrl-test-disabled": True,
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-area":
1020 dut = ctrl_panel.get("dd-ctrl-dut-value")
1021 phy = ctrl_panel.get("dd-ctrl-phy-value")
1022 area = self.spec_tbs[dut][phy][dd_area]
1024 [{"label": v, "value": v} for v in area.keys()],
1025 key=lambda d: d["label"]
1032 "dd-ctrl-area-value": dd_area,
1033 "dd-ctrl-test-value": str(),
1034 "dd-ctrl-test-options": options,
1035 "dd-ctrl-test-disabled": disabled,
1036 "cl-ctrl-core-options": list(),
1037 "cl-ctrl-core-value": list(),
1038 "cl-ctrl-core-all-value": list(),
1039 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
1040 "cl-ctrl-framesize-options": list(),
1041 "cl-ctrl-framesize-value": list(),
1042 "cl-ctrl-framesize-all-value": list(),
1043 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
1044 "cl-ctrl-testtype-options": list(),
1045 "cl-ctrl-testtype-value": list(),
1046 "cl-ctrl-testtype-all-value": list(),
1047 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
1049 elif trigger_id == "dd-ctrl-test":
1051 framesize_opts = list()
1052 testtype_opts = list()
1053 dut = ctrl_panel.get("dd-ctrl-dut-value")
1054 phy = ctrl_panel.get("dd-ctrl-phy-value")
1055 area = ctrl_panel.get("dd-ctrl-area-value")
1056 test = self.spec_tbs[dut][phy][area][dd_test]
1057 cores = test["core"]
1058 fsizes = test["frame-size"]
1059 ttypes = test["test-type"]
1060 if dut and phy and area and dd_test:
1061 core_opts = [{"label": v, "value": v}
1062 for v in sorted(cores)]
1063 framesize_opts = [{"label": v, "value": v}
1064 for v in sorted(fsizes)]
1065 testtype_opts = [{"label": v, "value": v}
1066 for v in sorted(ttypes)]
1068 "dd-ctrl-test-value": dd_test,
1069 "cl-ctrl-core-options": core_opts,
1070 "cl-ctrl-core-value": list(),
1071 "cl-ctrl-core-all-value": list(),
1072 "cl-ctrl-core-all-options": self.CL_ALL_ENABLED,
1073 "cl-ctrl-framesize-options": framesize_opts,
1074 "cl-ctrl-framesize-value": list(),
1075 "cl-ctrl-framesize-all-value": list(),
1076 "cl-ctrl-framesize-all-options": self.CL_ALL_ENABLED,
1077 "cl-ctrl-testtype-options": testtype_opts,
1078 "cl-ctrl-testtype-value": list(),
1079 "cl-ctrl-testtype-all-value": list(),
1080 "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
1082 elif trigger_id == "cl-ctrl-core":
1083 val_sel, val_all = self._sync_checklists(
1084 opt=ctrl_panel.get("cl-ctrl-core-options"),
1090 "cl-ctrl-core-value": val_sel,
1091 "cl-ctrl-core-all-value": val_all,
1093 elif trigger_id == "cl-ctrl-core-all":
1094 val_sel, val_all = self._sync_checklists(
1095 opt = ctrl_panel.get("cl-ctrl-core-options"),
1101 "cl-ctrl-core-value": val_sel,
1102 "cl-ctrl-core-all-value": val_all,
1104 elif trigger_id == "cl-ctrl-framesize":
1105 val_sel, val_all = self._sync_checklists(
1106 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1112 "cl-ctrl-framesize-value": val_sel,
1113 "cl-ctrl-framesize-all-value": val_all,
1115 elif trigger_id == "cl-ctrl-framesize-all":
1116 val_sel, val_all = self._sync_checklists(
1117 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1119 all=cl_framesize_all,
1123 "cl-ctrl-framesize-value": val_sel,
1124 "cl-ctrl-framesize-all-value": val_all,
1126 elif trigger_id == "cl-ctrl-testtype":
1127 val_sel, val_all = self._sync_checklists(
1128 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1134 "cl-ctrl-testtype-value": val_sel,
1135 "cl-ctrl-testtype-all-value": val_all,
1137 elif trigger_id == "cl-ctrl-testtype-all":
1138 val_sel, val_all = self._sync_checklists(
1139 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1141 all=cl_testtype_all,
1145 "cl-ctrl-testtype-value": val_sel,
1146 "cl-ctrl-testtype-all-value": val_all,
1148 elif trigger_id == "btn-ctrl-add":
1150 dut = ctrl_panel.get("dd-ctrl-dut-value")
1151 phy = ctrl_panel.get("dd-ctrl-phy-value")
1152 area = ctrl_panel.get("dd-ctrl-area-value")
1153 test = ctrl_panel.get("dd-ctrl-test-value")
1154 cores = ctrl_panel.get("cl-ctrl-core-value")
1155 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1156 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1157 # Add selected test to the list of tests in store:
1158 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1159 if store_sel is None:
1162 for framesize in framesizes:
1163 for ttype in testtypes:
1167 dut, phy.replace('af_xdp', 'af-xdp'), area,
1168 framesize.lower(), core.lower(), test,
1171 if tid not in [itm["id"] for itm in store_sel]:
1178 "framesize": framesize.lower(),
1179 "core": core.lower(),
1180 "testtype": ttype.lower()
1182 store_sel = sorted(store_sel, key=lambda d: d["id"])
1183 row_card_sel_tests = self.STYLE_ENABLED
1184 row_btns_sel_tests = self.STYLE_ENABLED
1185 if self.CLEAR_ALL_INPUTS:
1186 ctrl_panel.set(ctrl_panel.defaults)
1187 elif trigger_id == "btn-sel-remove-all":
1189 row_fig_tput = self.PLACEHOLDER
1190 row_fig_lat = self.PLACEHOLDER
1191 row_btn_dwnld = self.PLACEHOLDER
1192 row_card_sel_tests = self.STYLE_DISABLED
1193 row_btns_sel_tests = self.STYLE_DISABLED
1195 ctrl_panel.set({"cl-selected-options": list()})
1196 elif trigger_id == "btn-sel-remove":
1199 new_store_sel = list()
1200 for item in store_sel:
1201 if item["id"] not in list_sel:
1202 new_store_sel.append(item)
1203 store_sel = new_store_sel
1204 elif trigger_id == "url":
1205 # TODO: Add verification
1206 url_params = parsed_url["params"]
1208 store_sel = literal_eval(
1209 url_params.get("store_sel", list())[0])
1210 d_start = self._get_date(url_params.get("start", list())[0])
1211 d_end = self._get_date(url_params.get("end", list())[0])
1213 row_card_sel_tests = self.STYLE_ENABLED
1214 row_btns_sel_tests = self.STYLE_ENABLED
1216 if trigger_id in ("btn-ctrl-add", "url", "dpr-period",
1217 "btn-sel-remove", "cl-ctrl-normalize"):
1219 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1220 _generate_plotting_area(
1221 graph_trending(self.data, store_sel, self.layout,
1222 d_start, d_end, bool(cl_normalize)),
1223 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1226 "cl-selected-options": self._list_tests(store_sel)
1229 row_fig_tput = self.PLACEHOLDER
1230 row_fig_lat = self.PLACEHOLDER
1231 row_btn_dwnld = self.PLACEHOLDER
1232 row_card_sel_tests = self.STYLE_DISABLED
1233 row_btns_sel_tests = self.STYLE_DISABLED
1235 ctrl_panel.set({"cl-selected-options": list()})
1237 if ctrl_panel.get("cl-ctrl-core-value") and \
1238 ctrl_panel.get("cl-ctrl-framesize-value") and \
1239 ctrl_panel.get("cl-ctrl-testtype-value"):
1244 "btn-ctrl-add-disabled": disabled,
1245 "cl-normalize-value": cl_normalize
1249 ctrl_panel.panel, store_sel,
1250 row_fig_tput, row_fig_lat, row_btn_dwnld,
1251 row_card_sel_tests, row_btns_sel_tests
1253 ret_val.extend(ctrl_panel.values())
1257 Output("metadata-tput-lat", "children"),
1258 Output("metadata-hdrh-graph", "children"),
1259 Output("offcanvas-metadata", "is_open"),
1260 Input({"type": "graph", "index": ALL}, "clickData"),
1261 prevent_initial_call=True
1263 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1268 callback_context.triggered[0]["prop_id"].split(".")[0]
1270 idx = 0 if trigger_id == "tput" else 1
1271 graph_data = graph_data[idx]["points"][0]
1272 except (JSONDecodeError, IndexError, KeyError, ValueError,
1276 metadata = no_update
1281 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1282 ) for x in graph_data.get("text", "").split("<br>")
1284 if trigger_id == "tput":
1285 title = "Throughput"
1286 elif trigger_id == "lat":
1288 hdrh_data = graph_data.get("customdata", None)
1291 class_name="gy-2 p-0",
1293 dbc.CardHeader(hdrh_data.pop("name")),
1294 dbc.CardBody(children=[
1296 id="hdrh-latency-graph",
1297 figure=graph_hdrh_latency(
1298 hdrh_data, self.layout
1306 class_name="gy-2 p-0",
1308 dbc.CardHeader(children=[
1310 target_id="tput-lat-metadata",
1312 style={"display": "inline-block"}
1317 id="tput-lat-metadata",
1319 children=[dbc.ListGroup(children, flush=True), ]
1325 return metadata, graph, True
1328 Output("download-data", "data"),
1329 State("selected-tests", "data"),
1330 Input("btn-download-data", "n_clicks"),
1331 prevent_initial_call=True
1333 def _download_data(store_sel, n_clicks):
1344 for itm in store_sel:
1345 sel_data = select_trending_data(self.data, itm)
1346 if sel_data is None:
1348 df = pd.concat([df, sel_data], ignore_index=True)
1350 return dcc.send_data_frame(df.to_csv, "trending_data.csv")