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, callback
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",
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",
197 id="btn-download-data",
198 children=["Download Data"],
199 style={"display": "block"}
201 dcc.Download(id="download-data"),
203 target_id="tput-metadata",
205 style={"display": "inline-block"}
210 children="**Throughput**",
211 style={"display": "inline-block"}
215 children="Click on data points in the graph"
218 style=self.STYLE_HIDEN
221 id="div-latency-metadata",
224 target_id="latency-metadata",
226 style={"display": "inline-block"}
231 children="**Latency**",
232 style={"display": "inline-block"}
235 id="latency-metadata",
236 children="Click on data points in the graph"
239 style=self.STYLE_HIDEN
242 id="div-latency-hdrh",
245 id="loading-hdrh-latency-graph",
248 id="graph-latency-hdrh"
254 style=self.STYLE_HIDEN
260 "vertical-align": "top",
261 "display": "inline-block",
267 def _add_ctrl_shown(self):
273 html.H5("Selected tests"),
275 id="container-selected-tests",
280 labelStyle={"display": "block"}
284 children="Remove Selected",
288 id="btn-sel-display",
297 def _add_ctrl_select(self):
301 id="div-ctrl-select",
303 html.H5("Physical Test Bed Topology, NIC and Driver"),
306 placeholder="Select a Physical Test Bed Topology...",
310 {"label": k, "value": k} for k in self.spec_tbs.keys()
316 placeholder="Select an Area...",
324 placeholder="Select a Test...",
332 html.H5("Number of Cores"),
334 id="cl-ctrl-core-all",
335 options=[{"label": "All", "value": "all"}, ],
336 labelStyle={"display": "inline-block"}
340 labelStyle={"display": "inline-block"}
343 style={"display": "none"}
346 id="div-ctrl-framesize",
348 html.H5("Frame Size"),
350 id="cl-ctrl-framesize-all",
351 options=[{"label": "All", "value": "all"}, ],
352 labelStyle={"display": "inline-block"}
355 id="cl-ctrl-framesize",
356 labelStyle={"display": "inline-block"}
359 style={"display": "none"}
362 id="div-ctrl-testtype",
364 html.H5("Test Type"),
366 id="cl-ctrl-testtype-all",
367 options=[{"label": "All", "value": "all"}, ],
368 labelStyle={"display": "inline-block"}
371 id="cl-ctrl-testtype",
372 labelStyle={"display": "inline-block"}
375 style={"display": "none"}
385 min_date_allowed=datetime.utcnow() - timedelta(days=180),
386 max_date_allowed=datetime.utcnow(),
387 initial_visible_month=datetime.utcnow(),
388 start_date=datetime.utcnow() - timedelta(days=180),
389 end_date=datetime.utcnow(),
390 display_format="D MMMM YY"
395 def callbacks(self, app):
398 Output("dd-ctrl-area", "options"),
399 Output("dd-ctrl-area", "disabled"),
400 Input("dd-ctrl-phy", "value"),
402 def _update_dd_area(phy):
411 {"label": self.spec_tbs[phy][v]["label"], "value": v}
412 for v in [v for v in self.spec_tbs[phy].keys()]
419 return options, disable
422 Output("dd-ctrl-test", "options"),
423 Output("dd-ctrl-test", "disabled"),
424 State("dd-ctrl-phy", "value"),
425 Input("dd-ctrl-area", "value"),
427 def _update_dd_test(phy, area):
436 {"label": v, "value": v}
437 for v in self.spec_tbs[phy][area]["test"]
444 return options, disable
447 Output("div-ctrl-core", "style"),
448 Output("cl-ctrl-core", "options"),
449 Output("div-ctrl-framesize", "style"),
450 Output("cl-ctrl-framesize", "options"),
451 Output("div-ctrl-testtype", "style"),
452 Output("cl-ctrl-testtype", "options"),
453 Output("btn-ctrl-add", "disabled"),
454 State("dd-ctrl-phy", "value"),
455 State("dd-ctrl-area", "value"),
456 Input("dd-ctrl-test", "value"),
458 def _update_btn_add(phy, area, test):
465 core_style = {"display": "none"}
467 framesize_style = {"display": "none"}
469 testtype_style = {"display": "none"}
472 if phy and area and test:
473 core_style = {"display": "block"}
475 {"label": v, "value": v}
476 for v in self.spec_tbs[phy][area]["core"]
478 framesize_style = {"display": "block"}
480 {"label": v, "value": v}
481 for v in self.spec_tbs[phy][area]["frame-size"]
483 testtype_style = {"display": "block"}
485 {"label": v, "value": v}
486 for v in self.spec_tbs[phy][area]["test-type"]
491 core_style, core_opts,
492 framesize_style, framesize_opts,
493 testtype_style, testtype_opts,
497 def _sync_checklists(opt, sel, all, id):
500 options = {v["value"] for v in opt}
501 input_id = callback_context.triggered[0]["prop_id"].split(".")[0]
503 all = ["all"] if set(sel) == options else list()
505 sel = list(options) if all else list()
509 Output("cl-ctrl-core", "value"),
510 Output("cl-ctrl-core-all", "value"),
511 State("cl-ctrl-core", "options"),
512 Input("cl-ctrl-core", "value"),
513 Input("cl-ctrl-core-all", "value"),
514 prevent_initial_call=True
516 def _sync_cl_core(opt, sel, all):
517 return _sync_checklists(opt, sel, all, "cl-ctrl-core")
520 Output("cl-ctrl-framesize", "value"),
521 Output("cl-ctrl-framesize-all", "value"),
522 State("cl-ctrl-framesize", "options"),
523 Input("cl-ctrl-framesize", "value"),
524 Input("cl-ctrl-framesize-all", "value"),
525 prevent_initial_call=True
527 def _sync_cl_framesize(opt, sel, all):
528 return _sync_checklists(opt, sel, all, "cl-ctrl-framesize")
531 Output("cl-ctrl-testtype", "value"),
532 Output("cl-ctrl-testtype-all", "value"),
533 State("cl-ctrl-testtype", "options"),
534 Input("cl-ctrl-testtype", "value"),
535 Input("cl-ctrl-testtype-all", "value"),
536 prevent_initial_call=True
538 def _sync_cl_testtype(opt, sel, all):
539 return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
542 Output("graph-tput", "figure"),
543 Output("graph-tput", "style"),
544 Output("div-tput-metadata", "style"),
545 Output("graph-latency", "figure"),
546 Output("graph-latency", "style"),
547 Output("div-latency-metadata", "style"),
548 Output("selected-tests", "data"), # Store
549 Output("cl-selected", "options"), # User selection
550 Output("dd-ctrl-phy", "value"),
551 Output("dd-ctrl-area", "value"),
552 Output("dd-ctrl-test", "value"),
553 State("selected-tests", "data"), # Store
554 State("cl-selected", "value"),
555 State("dd-ctrl-phy", "value"),
556 State("dd-ctrl-area", "value"),
557 State("dd-ctrl-test", "value"),
558 State("cl-ctrl-core", "value"),
559 State("cl-ctrl-framesize", "value"),
560 State("cl-ctrl-testtype", "value"),
561 Input("btn-ctrl-add", "n_clicks"),
562 Input("btn-sel-display", "n_clicks"),
563 Input("btn-sel-remove", "n_clicks"),
564 Input("dpr-period", "start_date"),
565 Input("dpr-period", "end_date"),
566 prevent_initial_call=True
568 def _process_list(store_sel, list_sel, phy, area, test, cores,
569 framesizes, testtypes, btn_add, btn_display, btn_remove,
574 if not (btn_add or btn_display or btn_remove or d_start or d_end):
578 # Display selected tests with checkboxes:
581 {"label": v["id"], "value": v["id"]} for v in store_sel
587 def __init__(self) -> None:
589 "graph-tput-figure": no_update,
590 "graph-tput-style": no_update,
591 "div-tput-metadata-style": no_update,
592 "graph-lat-figure": no_update,
593 "graph-lat-style": no_update,
594 "div-lat-metadata-style": no_update,
595 "selected-tests-data": no_update,
596 "cl-selected-options": no_update,
597 "dd-ctrl-phy-value": no_update,
598 "dd-ctrl-area-value": no_update,
599 "dd-ctrl-test-value": no_update,
603 return tuple(self._output.values())
605 def set_values(self, kwargs: dict) -> None:
606 for key, val in kwargs.items():
607 if key in self._output:
608 self._output[key] = val
610 raise KeyError(f"The key {key} is not defined.")
613 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
615 d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
617 d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
619 output = RetunValue()
621 if trigger_id == "btn-ctrl-add":
622 # Add selected test to the list of tests in store:
623 if phy and area and test and cores and framesizes and testtypes:
624 if store_sel is None:
627 for framesize in framesizes:
628 for ttype in testtypes:
630 f"{phy.replace('af_xdp', 'af-xdp')}-"
632 f"{framesize.lower()}-"
637 if tid not in [itm["id"] for itm in store_sel]:
643 "framesize": framesize.lower(),
644 "core": core.lower(),
645 "testtype": ttype.lower()
648 "selected-tests-data": store_sel,
649 "cl-selected-options": _list_tests(),
650 "dd-ctrl-phy-value": None,
651 "dd-ctrl-area-value": None,
652 "dd-ctrl-test-value": None,
655 elif trigger_id in ("btn-sel-display", "dpr-period"):
656 fig_tput, fig_lat = graph_trending(
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
674 elif trigger_id == "btn-sel-remove":
676 new_store_sel = list()
677 for item in store_sel:
678 if item["id"] not in list_sel:
679 new_store_sel.append(item)
680 store_sel = new_store_sel
682 fig_tput, fig_lat = graph_trending(
683 self.data, store_sel, self.layout, d_start, d_end
686 "graph-tput-figure": \
687 fig_tput if fig_tput else self.NO_GRAPH,
688 "graph-tput-style": \
689 self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
690 "div-tput-metadata-style": \
691 self.STYLE_INLINE if fig_tput else self.STYLE_HIDEN,
692 "graph-lat-figure": \
693 fig_lat if fig_lat else self.NO_GRAPH,
695 self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
696 "div-lat-metadata-style": \
697 self.STYLE_INLINE if fig_lat else self.STYLE_HIDEN,
698 "selected-tests-data": store_sel,
699 "cl-selected-options": _list_tests()
703 "graph-tput-figure": self.NO_GRAPH,
704 "graph-tput-style": self.STYLE_HIDEN,
705 "div-tput-metadata-style": self.STYLE_HIDEN,
706 "graph-lat-figure": self.NO_GRAPH,
707 "graph-lat-style": self.STYLE_HIDEN,
708 "div-lat-metadata-style": self.STYLE_HIDEN,
709 "selected-tests-data": store_sel,
710 "cl-selected-options": _list_tests()
713 return output.value()
716 Output("tput-metadata", "children"),
717 Input("graph-tput", "clickData")
719 def _show_tput_metadata(hover_data):
725 return hover_data["points"][0]["text"].replace("<br>", "\n"),
728 Output("latency-metadata", "children"),
729 Output("graph-latency-hdrh", "figure"),
730 Output("div-latency-hdrh", "style"),
731 Input("graph-latency", "clickData")
733 def _show_latency_metadata(hover_data):
740 hdrh_data = hover_data["points"][0].get("customdata", None)
742 graph = graph_hdrh_latency(hdrh_data, self.layout)
745 hover_data["points"][0]["text"].replace("<br>", "\n"),
747 self.STYLE_INLINE if graph else self.STYLE_HIDEN
751 Output("download-data", "data"),
752 State("selected-tests", "data"),
753 Input("btn-download-data", "n_clicks"),
754 prevent_initial_call=True
756 def _download_data(store_sel, n_clicks):
764 for itm in store_sel:
765 sel_data = select_trending_data(self.data, itm)
768 df = pd.concat([df, sel_data], ignore_index=True)
770 return dcc.send_data_frame(df.to_csv, "trending_data.csv")