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.
21 from dash import callback_context, no_update
22 from dash import Input, Output, State
23 from dash.exceptions import PreventUpdate
24 from yaml import load, FullLoader, YAMLError
25 from datetime import datetime, timedelta
27 from ..data.data import Data
28 from .graphs import graph_trending, graph_hdrh_latency, \
36 STYLE_HIDEN = {"display": "none"}
37 STYLE_BLOCK = {"display": "block", "vertical-align": "top"}
39 "display": "inline-block",
40 "vertical-align": "top"
42 NO_GRAPH = {"data": [], "layout": {}, "frames": []}
44 def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
51 self._html_layout_file = html_layout_file
52 self._spec_file = spec_file
53 self._graph_layout_file = graph_layout_file
54 self._data_spec_file = data_spec_file
58 data_spec_file=self._data_spec_file,
63 data_spec_file=self._data_spec_file,
65 ).read_trending_ndrpdr()
67 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
70 self._html_layout = ""
72 self._graph_layout = None
75 with open(self._html_layout_file, "r") as file_read:
76 self._html_layout = file_read.read()
77 except IOError as err:
79 f"Not possible to open the file {self._html_layout_file}\n{err}"
83 with open(self._spec_file, "r") as file_read:
84 self._spec_tbs = load(file_read, Loader=FullLoader)
85 except IOError as err:
87 f"Not possible to open the file {self._spec_file,}\n{err}"
89 except YAMLError as err:
91 f"An error occurred while parsing the specification file "
92 f"{self._spec_file,}\n"
97 with open(self._graph_layout_file, "r") as file_read:
98 self._graph_layout = load(file_read, Loader=FullLoader)
99 except IOError as err:
101 f"Not possible to open the file {self._graph_layout_file}\n"
104 except YAMLError as err:
106 f"An error occurred while parsing the specification file "
107 f"{self._graph_layout_file}\n"
112 if self._app is not None and hasattr(self, 'callbacks'):
113 self.callbacks(self._app)
116 def html_layout(self):
117 return self._html_layout
121 return self._spec_tbs
129 return self._graph_layout
131 def add_content(self):
134 if self.html_layout and self.spec_tbs:
138 dcc.Store(id="selected-tests"),
139 self._add_ctrl_div(),
140 self._add_plotting_div()
146 children="An Error Occured."
149 def _add_ctrl_div(self):
150 """Add div with controls. It is placed on the left side.
156 id="div-controls-tabs",
158 self._add_ctrl_select(),
159 self._add_ctrl_shown()
164 "display": "inline-block",
169 def _add_plotting_div(self):
170 """Add div with plots and tables. It is placed on the right side.
173 id="div-plotting-area",
175 html.Table(children=[
178 style=self.STYLE_HIDEN,
186 ], style={"width": "80%"}),
189 target_id="tput-metadata",
191 style={"display": "inline-block"}
196 children="**Throughput**",
197 style={"display": "inline-block"}
201 children="Click on data point in the graph"
204 id="div-lat-metadata",
205 style=self.STYLE_HIDEN,
208 target_id="lat-metadata",
210 style={"display": "inline-block"}
215 children="**Latency**",
216 style={"display": "inline-block"}
221 "Click on data point in the graph"
225 ], style={"width": "20%"}),
230 style=self.STYLE_HIDEN,
238 ], style={"width": "80%"}),
242 id="graph-latency-hdrh",
243 style=self.STYLE_INLINE,
247 ], style={"width": "20%"}),
252 style=self.STYLE_HIDEN,
258 id="btn-download-data",
259 children=["Download Data"]
261 dcc.Download(id="download-data")
264 ], style={"width": "80%"}),
267 ], style={"width": "20%"}),
273 "vertical-align": "top",
274 "display": "inline-block",
279 def _add_ctrl_shown(self):
285 html.H5("Selected tests"),
287 id="container-selected-tests",
292 labelStyle={"display": "block"}
296 children="Remove Selected",
300 id="btn-sel-display",
309 def _add_ctrl_select(self):
313 id="div-ctrl-select",
315 html.H5("Physical Test Bed Topology, NIC and Driver"),
318 placeholder="Select a Physical Test Bed Topology...",
322 {"label": k, "value": k} for k in self.spec_tbs.keys()
328 placeholder="Select an Area...",
336 placeholder="Select a Test...",
344 html.H5("Number of Cores"),
346 id="cl-ctrl-core-all",
347 options=[{"label": "All", "value": "all"}, ],
348 labelStyle={"display": "inline-block"}
352 labelStyle={"display": "inline-block"}
355 style={"display": "none"}
358 id="div-ctrl-framesize",
360 html.H5("Frame Size"),
362 id="cl-ctrl-framesize-all",
363 options=[{"label": "All", "value": "all"}, ],
364 labelStyle={"display": "inline-block"}
367 id="cl-ctrl-framesize",
368 labelStyle={"display": "inline-block"}
371 style={"display": "none"}
374 id="div-ctrl-testtype",
376 html.H5("Test Type"),
378 id="cl-ctrl-testtype-all",
379 options=[{"label": "All", "value": "all"}, ],
380 labelStyle={"display": "inline-block"}
383 id="cl-ctrl-testtype",
384 labelStyle={"display": "inline-block"}
387 style={"display": "none"}
397 min_date_allowed=datetime.utcnow() - timedelta(days=180),
398 max_date_allowed=datetime.utcnow(),
399 initial_visible_month=datetime.utcnow(),
400 start_date=datetime.utcnow() - timedelta(days=180),
401 end_date=datetime.utcnow(),
402 display_format="D MMMM YY"
407 def callbacks(self, app):
410 Output("dd-ctrl-area", "options"),
411 Output("dd-ctrl-area", "disabled"),
412 Input("dd-ctrl-phy", "value"),
414 def _update_dd_area(phy):
423 {"label": self.spec_tbs[phy][v]["label"], "value": v}
424 for v in [v for v in self.spec_tbs[phy].keys()]
431 return options, disable
434 Output("dd-ctrl-test", "options"),
435 Output("dd-ctrl-test", "disabled"),
436 State("dd-ctrl-phy", "value"),
437 Input("dd-ctrl-area", "value"),
439 def _update_dd_test(phy, area):
448 {"label": v, "value": v}
449 for v in self.spec_tbs[phy][area]["test"]
456 return options, disable
459 Output("div-ctrl-core", "style"),
460 Output("cl-ctrl-core", "options"),
461 Output("div-ctrl-framesize", "style"),
462 Output("cl-ctrl-framesize", "options"),
463 Output("div-ctrl-testtype", "style"),
464 Output("cl-ctrl-testtype", "options"),
465 Output("btn-ctrl-add", "disabled"),
466 State("dd-ctrl-phy", "value"),
467 State("dd-ctrl-area", "value"),
468 Input("dd-ctrl-test", "value"),
470 def _update_btn_add(phy, area, test):
477 core_style = {"display": "none"}
479 framesize_style = {"display": "none"}
481 testtype_style = {"display": "none"}
484 if phy and area and test:
485 core_style = {"display": "block"}
487 {"label": v, "value": v}
488 for v in self.spec_tbs[phy][area]["core"]
490 framesize_style = {"display": "block"}
492 {"label": v, "value": v}
493 for v in self.spec_tbs[phy][area]["frame-size"]
495 testtype_style = {"display": "block"}
497 {"label": v, "value": v}
498 for v in self.spec_tbs[phy][area]["test-type"]
503 core_style, core_opts,
504 framesize_style, framesize_opts,
505 testtype_style, testtype_opts,
509 def _sync_checklists(opt, sel, all, id):
512 options = {v["value"] for v in opt}
513 input_id = callback_context.triggered[0]["prop_id"].split(".")[0]
515 all = ["all"] if set(sel) == options else list()
517 sel = list(options) if all else list()
521 Output("cl-ctrl-core", "value"),
522 Output("cl-ctrl-core-all", "value"),
523 State("cl-ctrl-core", "options"),
524 Input("cl-ctrl-core", "value"),
525 Input("cl-ctrl-core-all", "value"),
526 prevent_initial_call=True
528 def _sync_cl_core(opt, sel, all):
529 return _sync_checklists(opt, sel, all, "cl-ctrl-core")
532 Output("cl-ctrl-framesize", "value"),
533 Output("cl-ctrl-framesize-all", "value"),
534 State("cl-ctrl-framesize", "options"),
535 Input("cl-ctrl-framesize", "value"),
536 Input("cl-ctrl-framesize-all", "value"),
537 prevent_initial_call=True
539 def _sync_cl_framesize(opt, sel, all):
540 return _sync_checklists(opt, sel, all, "cl-ctrl-framesize")
543 Output("cl-ctrl-testtype", "value"),
544 Output("cl-ctrl-testtype-all", "value"),
545 State("cl-ctrl-testtype", "options"),
546 Input("cl-ctrl-testtype", "value"),
547 Input("cl-ctrl-testtype-all", "value"),
548 prevent_initial_call=True
550 def _sync_cl_testtype(opt, sel, all):
551 return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
554 Output("graph-tput", "figure"),
555 Output("graph-latency", "figure"),
556 Output("div-tput", "style"),
557 Output("div-latency", "style"),
558 Output("div-lat-metadata", "style"),
559 Output("div-download", "style"),
560 Output("selected-tests", "data"), # Store
561 Output("cl-selected", "options"), # User selection
562 Output("dd-ctrl-phy", "value"),
563 Output("dd-ctrl-area", "value"),
564 Output("dd-ctrl-test", "value"),
565 State("selected-tests", "data"), # Store
566 State("cl-selected", "value"),
567 State("dd-ctrl-phy", "value"),
568 State("dd-ctrl-area", "value"),
569 State("dd-ctrl-test", "value"),
570 State("cl-ctrl-core", "value"),
571 State("cl-ctrl-framesize", "value"),
572 State("cl-ctrl-testtype", "value"),
573 Input("btn-ctrl-add", "n_clicks"),
574 Input("btn-sel-display", "n_clicks"),
575 Input("btn-sel-remove", "n_clicks"),
576 Input("dpr-period", "start_date"),
577 Input("dpr-period", "end_date"),
578 prevent_initial_call=True
580 def _process_list(store_sel, list_sel, phy, area, test, cores,
581 framesizes, testtypes, btn_add, btn_display, btn_remove,
586 if not (btn_add or btn_display or btn_remove or d_start or d_end):
590 # Display selected tests with checkboxes:
593 {"label": v["id"], "value": v["id"]} for v in store_sel
599 def __init__(self) -> None:
601 "graph-tput-figure": no_update,
602 "graph-lat-figure": no_update,
603 "div-tput-style": no_update,
604 "div-latency-style": no_update,
605 "div-lat-metadata-style": no_update,
606 "div-download-style": no_update,
607 "selected-tests-data": no_update,
608 "cl-selected-options": no_update,
609 "dd-ctrl-phy-value": no_update,
610 "dd-ctrl-area-value": no_update,
611 "dd-ctrl-test-value": no_update,
615 return tuple(self._output.values())
617 def set_values(self, kwargs: dict) -> None:
618 for key, val in kwargs.items():
619 if key in self._output:
620 self._output[key] = val
622 raise KeyError(f"The key {key} is not defined.")
625 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
627 d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
629 d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
631 output = RetunValue()
633 if trigger_id == "btn-ctrl-add":
634 # Add selected test to the list of tests in store:
635 if phy and area and test and cores and framesizes and testtypes:
636 if store_sel is None:
639 for framesize in framesizes:
640 for ttype in testtypes:
642 f"{phy.replace('af_xdp', 'af-xdp')}-"
644 f"{framesize.lower()}-"
649 if tid not in [itm["id"] for itm in store_sel]:
655 "framesize": framesize.lower(),
656 "core": core.lower(),
657 "testtype": ttype.lower()
660 "selected-tests-data": store_sel,
661 "cl-selected-options": _list_tests(),
662 "dd-ctrl-phy-value": None,
663 "dd-ctrl-area-value": None,
664 "dd-ctrl-test-value": None,
667 elif trigger_id in ("btn-sel-display", "dpr-period"):
668 fig_tput, fig_lat = graph_trending(
669 self.data, store_sel, self.layout, d_start, d_end
672 "graph-tput-figure": \
673 fig_tput if fig_tput else self.NO_GRAPH,
674 "graph-lat-figure": \
675 fig_lat if fig_lat else self.NO_GRAPH,
677 self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
678 "div-latency-style": \
679 self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
680 "div-lat-metadata-style": \
681 self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
682 "div-download-style": \
683 self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
686 elif trigger_id == "btn-sel-remove":
688 new_store_sel = list()
689 for item in store_sel:
690 if item["id"] not in list_sel:
691 new_store_sel.append(item)
692 store_sel = new_store_sel
694 fig_tput, fig_lat = graph_trending(
695 self.data, store_sel, self.layout, d_start, d_end
698 "graph-tput-figure": \
699 fig_tput if fig_tput else self.NO_GRAPH,
700 "graph-lat-figure": \
701 fig_lat if fig_lat else self.NO_GRAPH,
703 self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
704 "div-latency-style": \
705 self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
706 "div-lat-metadata-style": \
707 self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
708 "div-download-style": \
709 self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
710 "selected-tests-data": store_sel,
711 "cl-selected-options": _list_tests()
715 "graph-tput-figure": self.NO_GRAPH,
716 "graph-lat-figure": self.NO_GRAPH,
717 "div-tput-style": self.STYLE_HIDEN,
718 "div-latency-style": self.STYLE_HIDEN,
719 "div-lat-metadata-style": self.STYLE_HIDEN,
720 "div-download-style": self.STYLE_HIDEN,
721 "selected-tests-data": store_sel,
722 "cl-selected-options": _list_tests()
725 return output.value()
728 Output("tput-metadata", "children"),
729 Input("graph-tput", "clickData")
731 def _show_tput_metadata(hover_data):
737 return hover_data["points"][0]["text"].replace("<br>", "\n")
740 Output("graph-latency-hdrh", "figure"),
741 Output("graph-latency-hdrh", "style"),
742 Output("lat-metadata", "children"),
743 Input("graph-latency", "clickData")
745 def _show_latency_hdhr(hover_data):
752 hdrh_data = hover_data["points"][0].get("customdata", None)
754 graph = graph_hdrh_latency(hdrh_data, self.layout)
759 hover_data["points"][0]["text"].replace("<br>", "\n")
763 Output("download-data", "data"),
764 State("selected-tests", "data"),
765 Input("btn-download-data", "n_clicks"),
766 prevent_initial_call=True
768 def _download_data(store_sel, n_clicks):
776 for itm in store_sel:
777 sel_data = select_trending_data(self.data, itm)
780 df = pd.concat([df, sel_data], ignore_index=True)
782 return dcc.send_data_frame(df.to_csv, "trending_data.csv")