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.
22 from dash import callback_context, no_update
23 from dash import Input, Output, State, callback
24 from dash.exceptions import PreventUpdate
25 from yaml import load, FullLoader, YAMLError
26 from datetime import datetime, timedelta
28 from ..data.data import Data
29 from .graphs import graph_trending_tput, graph_hdrh_latency
36 STYLE_HIDEN = {"display": "none"}
37 STYLE_BLOCK = {"display": "block", "vertical-align": "top"}
39 "display": "inline-block",
40 "vertical-align": "top",
43 NO_GRAPH = {"data": [], "layout": {}, "frames": []}
45 def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
52 self._html_layout_file = html_layout_file
53 self._spec_file = spec_file
54 self._graph_layout_file = graph_layout_file
55 self._data_spec_file = data_spec_file
59 data_spec_file=self._data_spec_file,
64 data_spec_file=self._data_spec_file,
66 ).read_trending_ndrpdr()
68 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
71 self._html_layout = ""
73 self._graph_layout = None
76 with open(self._html_layout_file, "r") as file_read:
77 self._html_layout = file_read.read()
78 except IOError as err:
80 f"Not possible to open the file {self._html_layout_file}\n{err}"
84 with open(self._spec_file, "r") as file_read:
85 self._spec_tbs = load(file_read, Loader=FullLoader)
86 except IOError as err:
88 f"Not possible to open the file {self._spec_file,}\n{err}"
90 except YAMLError as err:
92 f"An error occurred while parsing the specification file "
93 f"{self._spec_file,}\n"
98 with open(self._graph_layout_file, "r") as file_read:
99 self._graph_layout = load(file_read, Loader=FullLoader)
100 except IOError as err:
102 f"Not possible to open the file {self._graph_layout_file}\n"
105 except YAMLError as err:
107 f"An error occurred while parsing the specification file "
108 f"{self._graph_layout_file}\n"
113 if self._app is not None and hasattr(self, 'callbacks'):
114 self.callbacks(self._app)
117 def html_layout(self):
118 return self._html_layout
122 return self._spec_tbs
130 return self._graph_layout
132 def add_content(self):
135 if self.html_layout and self.spec_tbs:
139 dcc.Store(id="selected-tests"),
140 self._add_ctrl_div(),
141 self._add_plotting_div()
147 children="An Error Occured."
150 def _add_ctrl_div(self):
151 """Add div with controls. It is placed on the left side.
157 id="div-controls-tabs",
159 self._add_ctrl_select(),
160 self._add_ctrl_shown()
165 "display": "inline-block",
171 def _add_plotting_div(self):
172 """Add div with plots and tables. It is placed on the right side.
175 id="div-plotting-area",
182 style=self.STYLE_HIDEN
186 style=self.STYLE_HIDEN
194 id="div-tput-metadata",
196 dcc.Markdown("**Throughput**"),
199 children="Click on data points in the graph"
202 style=self.STYLE_HIDEN
205 id="div-latency-metadata",
207 dcc.Markdown("**Latency**"),
209 id="latency-metadata",
210 children="Click on data points in the graph"
213 style=self.STYLE_HIDEN
216 id="div-latency-hdrh",
219 id="loading-hdrh-latency-graph",
222 id="graph-latency-hdrh"
228 style=self.STYLE_HIDEN
234 "vertical-align": "top",
235 "display": "inline-block",
241 def _add_ctrl_shown(self):
247 html.H5("Selected tests"),
249 id="container-selected-tests",
254 labelStyle={"display": "block"}
258 children="Remove Selected",
262 id="btn-sel-display",
271 def _add_ctrl_select(self):
275 id="div-ctrl-select",
277 html.H5("Physical Test Bed Topology, NIC and Driver"),
280 placeholder="Select a Physical Test Bed Topology...",
284 {"label": k, "value": k} for k in self.spec_tbs.keys()
290 placeholder="Select an Area...",
298 placeholder="Select a Test...",
306 html.H5("Number of Cores"),
308 id="cl-ctrl-core-all",
309 options=[{"label": "All", "value": "all"}, ],
310 labelStyle={"display": "inline-block"}
314 labelStyle={"display": "inline-block"}
317 style={"display": "none"}
320 id="div-ctrl-framesize",
322 html.H5("Frame Size"),
324 id="cl-ctrl-framesize-all",
325 options=[{"label": "All", "value": "all"}, ],
326 labelStyle={"display": "inline-block"}
329 id="cl-ctrl-framesize",
330 labelStyle={"display": "inline-block"}
333 style={"display": "none"}
336 id="div-ctrl-testtype",
338 html.H5("Test Type"),
340 id="cl-ctrl-testtype-all",
341 options=[{"label": "All", "value": "all"}, ],
342 labelStyle={"display": "inline-block"}
345 id="cl-ctrl-testtype",
346 labelStyle={"display": "inline-block"}
349 style={"display": "none"}
359 min_date_allowed=datetime.utcnow() - timedelta(days=180),
360 max_date_allowed=datetime.utcnow(),
361 initial_visible_month=datetime.utcnow(),
362 start_date=datetime.utcnow() - timedelta(days=180),
363 end_date=datetime.utcnow(),
364 display_format="D MMMM YY"
369 def callbacks(self, app):
372 Output("dd-ctrl-area", "options"),
373 Output("dd-ctrl-area", "disabled"),
374 Input("dd-ctrl-phy", "value"),
376 def _update_dd_area(phy):
385 {"label": self.spec_tbs[phy][v]["label"], "value": v}
386 for v in [v for v in self.spec_tbs[phy].keys()]
393 return options, disable
396 Output("dd-ctrl-test", "options"),
397 Output("dd-ctrl-test", "disabled"),
398 State("dd-ctrl-phy", "value"),
399 Input("dd-ctrl-area", "value"),
401 def _update_dd_test(phy, area):
410 {"label": v, "value": v}
411 for v in self.spec_tbs[phy][area]["test"]
418 return options, disable
421 Output("div-ctrl-core", "style"),
422 Output("cl-ctrl-core", "options"),
423 Output("div-ctrl-framesize", "style"),
424 Output("cl-ctrl-framesize", "options"),
425 Output("div-ctrl-testtype", "style"),
426 Output("cl-ctrl-testtype", "options"),
427 Output("btn-ctrl-add", "disabled"),
428 State("dd-ctrl-phy", "value"),
429 State("dd-ctrl-area", "value"),
430 Input("dd-ctrl-test", "value"),
432 def _update_btn_add(phy, area, test):
439 core_style = {"display": "none"}
441 framesize_style = {"display": "none"}
443 testtype_style = {"display": "none"}
446 if phy and area and test:
447 core_style = {"display": "block"}
449 {"label": v, "value": v}
450 for v in self.spec_tbs[phy][area]["core"]
452 framesize_style = {"display": "block"}
454 {"label": v, "value": v}
455 for v in self.spec_tbs[phy][area]["frame-size"]
457 testtype_style = {"display": "block"}
459 {"label": v, "value": v}
460 for v in self.spec_tbs[phy][area]["test-type"]
465 core_style, core_opts,
466 framesize_style, framesize_opts,
467 testtype_style, testtype_opts,
471 def _sync_checklists(opt, sel, all, id):
474 options = {v["value"] for v in opt}
475 input_id = callback_context.triggered[0]["prop_id"].split(".")[0]
477 all = ["all"] if set(sel) == options else list()
479 sel = list(options) if all else list()
483 Output("cl-ctrl-core", "value"),
484 Output("cl-ctrl-core-all", "value"),
485 State("cl-ctrl-core", "options"),
486 Input("cl-ctrl-core", "value"),
487 Input("cl-ctrl-core-all", "value"),
488 prevent_initial_call=True
490 def _sync_cl_core(opt, sel, all):
491 return _sync_checklists(opt, sel, all, "cl-ctrl-core")
494 Output("cl-ctrl-framesize", "value"),
495 Output("cl-ctrl-framesize-all", "value"),
496 State("cl-ctrl-framesize", "options"),
497 Input("cl-ctrl-framesize", "value"),
498 Input("cl-ctrl-framesize-all", "value"),
499 prevent_initial_call=True
501 def _sync_cl_framesize(opt, sel, all):
502 return _sync_checklists(opt, sel, all, "cl-ctrl-framesize")
505 Output("cl-ctrl-testtype", "value"),
506 Output("cl-ctrl-testtype-all", "value"),
507 State("cl-ctrl-testtype", "options"),
508 Input("cl-ctrl-testtype", "value"),
509 Input("cl-ctrl-testtype-all", "value"),
510 prevent_initial_call=True
512 def _sync_cl_testtype(opt, sel, all):
513 return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
516 Output("graph-tput", "figure"),
517 Output("graph-tput", "style"),
518 Output("div-tput-metadata", "style"),
519 Output("graph-latency", "figure"),
520 Output("graph-latency", "style"),
521 Output("div-latency-metadata", "style"),
522 Output("selected-tests", "data"), # Store
523 Output("cl-selected", "options"), # User selection
524 Output("dd-ctrl-phy", "value"),
525 Output("dd-ctrl-area", "value"),
526 Output("dd-ctrl-test", "value"),
527 State("selected-tests", "data"), # Store
528 State("cl-selected", "value"),
529 State("dd-ctrl-phy", "value"),
530 State("dd-ctrl-area", "value"),
531 State("dd-ctrl-test", "value"),
532 State("cl-ctrl-core", "value"),
533 State("cl-ctrl-framesize", "value"),
534 State("cl-ctrl-testtype", "value"),
535 Input("btn-ctrl-add", "n_clicks"),
536 Input("btn-sel-display", "n_clicks"),
537 Input("btn-sel-remove", "n_clicks"),
538 Input("dpr-period", "start_date"),
539 Input("dpr-period", "end_date"),
540 prevent_initial_call=True
542 def _process_list(store_sel, list_sel, phy, area, test, cores,
543 framesizes, testtypes, btn_add, btn_display, btn_remove,
548 if not (btn_add or btn_display or btn_remove or d_start or d_end):
552 # Display selected tests with checkboxes:
555 {"label": v["id"], "value": v["id"]} for v in store_sel
561 def __init__(self) -> None:
563 "graph-tput-figure": no_update,
564 "graph-tput-style": no_update,
565 "div-tput-metadata-style": no_update,
566 "graph-lat-figure": no_update,
567 "graph-lat-style": no_update,
568 "div-lat-metadata-style": no_update,
569 "selected-tests-data": no_update,
570 "cl-selected-options": no_update,
571 "dd-ctrl-phy-value": no_update,
572 "dd-ctrl-area-value": no_update,
573 "dd-ctrl-test-value": no_update,
577 return tuple(self._output.values())
579 def set_values(self, kwargs: dict) -> None:
580 for key, val in kwargs.items():
581 if key in self._output:
582 self._output[key] = val
584 raise KeyError(f"The key {key} is not defined.")
587 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
589 d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
591 d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
593 output = RetunValue()
595 if trigger_id == "btn-ctrl-add":
596 # Add selected test to the list of tests in store:
597 if phy and area and test and cores and framesizes and testtypes:
598 if store_sel is None:
601 for framesize in framesizes:
602 for ttype in testtypes:
604 f"{phy.replace('af_xdp', 'af-xdp')}-"
606 f"{framesize.lower()}-"
611 if tid not in [itm["id"] for itm in store_sel]:
617 "framesize": framesize.lower(),
618 "core": core.lower(),
619 "testtype": ttype.lower()
622 "selected-tests-data": store_sel,
623 "cl-selected-options": _list_tests(),
624 "dd-ctrl-phy-value": None,
625 "dd-ctrl-area-value": None,
626 "dd-ctrl-test-value": None,
629 elif trigger_id in ("btn-sel-display", "dpr-period"):
630 fig_tput, fig_lat = graph_trending_tput(
631 self.data, store_sel, self.layout, d_start, d_end
634 "graph-tput-figure": \
635 fig_tput if fig_tput else self.NO_GRAPH,
636 "graph-tput-style": \
637 self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
638 "div-tput-metadata-style": \
639 self.STYLE_INLINE if fig_tput else self.STYLE_HIDEN,
640 "graph-lat-figure": \
641 fig_lat if fig_lat else self.NO_GRAPH,
643 self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
644 "div-lat-metadata-style": \
645 self.STYLE_INLINE if fig_lat else self.STYLE_HIDEN
648 elif trigger_id == "btn-sel-remove":
650 new_store_sel = list()
651 for item in store_sel:
652 if item["id"] not in list_sel:
653 new_store_sel.append(item)
654 store_sel = new_store_sel
656 fig_tput, fig_lat = graph_trending_tput(
657 self.data, store_sel, self.layout, d_start, d_end
660 "graph-tput-figure": \
661 fig_tput if fig_tput else self.NO_GRAPH,
662 "graph-tput-style": \
663 self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
664 "div-tput-metadata-style": \
665 self.STYLE_INLINE if fig_tput else self.STYLE_HIDEN,
666 "graph-lat-figure": \
667 fig_lat if fig_lat else self.NO_GRAPH,
669 self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
670 "div-lat-metadata-style": \
671 self.STYLE_INLINE if fig_lat else self.STYLE_HIDEN,
672 "selected-tests-data": store_sel,
673 "cl-selected-options": _list_tests()
677 "graph-tput-figure": self.NO_GRAPH,
678 "graph-tput-style": self.STYLE_HIDEN,
679 "div-tput-metadata-style": self.STYLE_HIDEN,
680 "graph-lat-figure": self.NO_GRAPH,
681 "graph-lat-style": self.STYLE_HIDEN,
682 "div-lat-metadata-style": self.STYLE_HIDEN,
683 "selected-tests-data": store_sel,
684 "cl-selected-options": _list_tests()
687 return output.value()
690 Output("tput-metadata", "children"),
691 Input("graph-tput", "clickData")
693 def _show_tput_metadata(hover_data):
696 return hover_data["points"][0]["text"].replace("<br>", "\n"),
699 Output("latency-metadata", "children"),
700 Output("graph-latency-hdrh", "figure"),
701 Output("div-latency-hdrh", "style"),
702 Input("graph-latency", "clickData")
704 def _show_latency_metadata(hover_data):
707 graph = graph_hdrh_latency(
708 hover_data["points"][0]["customdata"], self.layout
713 hover_data["points"][0]["text"].replace("<br>", "\n"),
715 self.STYLE_INLINE if graph else self.STYLE_HIDEN