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.
23 from dash import callback_context, no_update
24 from dash import Input, Output, State
25 from dash.exceptions import PreventUpdate
26 import dash_bootstrap_components as dbc
27 from yaml import load, FullLoader, YAMLError
28 from datetime import datetime, timedelta
30 from ..data.data import Data
31 from .graphs import graph_trending, graph_hdrh_latency, \
39 STYLE_HIDEN = {"display": "none"}
40 STYLE_BLOCK = {"display": "block", "vertical-align": "top"}
42 "display": "inline-block",
43 "vertical-align": "top"
45 NO_GRAPH = {"data": [], "layout": {}, "frames": []}
47 def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
54 self._html_layout_file = html_layout_file
55 self._spec_file = spec_file
56 self._graph_layout_file = graph_layout_file
57 self._data_spec_file = data_spec_file
61 data_spec_file=self._data_spec_file,
63 ).read_trending_mrr(days=5)
66 data_spec_file=self._data_spec_file,
68 ).read_trending_ndrpdr(days=14)
70 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
73 self._html_layout = ""
75 self._graph_layout = None
78 with open(self._html_layout_file, "r") as file_read:
79 self._html_layout = file_read.read()
80 except IOError as err:
82 f"Not possible to open the file {self._html_layout_file}\n{err}"
86 with open(self._spec_file, "r") as file_read:
87 self._spec_tbs = load(file_read, Loader=FullLoader)
88 except IOError as err:
90 f"Not possible to open the file {self._spec_file,}\n{err}"
92 except YAMLError as err:
94 f"An error occurred while parsing the specification file "
95 f"{self._spec_file,}\n"
100 with open(self._graph_layout_file, "r") as file_read:
101 self._graph_layout = load(file_read, Loader=FullLoader)
102 except IOError as err:
104 f"Not possible to open the file {self._graph_layout_file}\n"
107 except YAMLError as err:
109 f"An error occurred while parsing the specification file "
110 f"{self._graph_layout_file}\n"
115 if self._app is not None and hasattr(self, 'callbacks'):
116 self.callbacks(self._app)
119 def html_layout(self):
120 return self._html_layout
124 return self._spec_tbs
132 return self._graph_layout
134 def add_content(self):
137 if self.html_layout and self.spec_tbs:
155 self._add_ctrl_col(),
156 self._add_plotting_col(),
160 id="offcanvas-metadata",
161 title="Throughput And Latency",
168 "This is the placeholder for metadata."
188 def _add_navbar(self):
189 """Add nav element with navigation panel. It is placed on the top.
191 return dbc.NavbarSimple(
192 id="navbarsimple-main",
196 "Continuous Performance Trending",
204 brand_external_link=True,
210 def _add_ctrl_col(self) -> dbc.Col:
211 """Add column with controls. It is placed on the left side.
216 self._add_ctrl_panel(),
217 self._add_ctrl_shown()
221 def _add_plotting_col(self) -> dbc.Col:
222 """Add column with plots and tables. It is placed on the right side.
225 id="col-plotting-area",
227 dbc.Row( # Throughput
232 dcc.Graph(id="graph-tput")
241 dcc.Graph(id="graph-latency")
249 dcc.Loading(children=[
251 id="btn-download-data",
252 children=["Download Data"]
254 dcc.Download(id="download-data")
262 def _add_ctrl_panel(self) -> dbc.Row:
269 dbc.Label("Physical Test Bed Topology, NIC and Driver"),
272 placeholder="Select a Physical Test Bed Topology...",
276 {"label": k, "value": k} for k in self.spec_tbs.keys()
282 placeholder="Select an Area...",
290 placeholder="Select a Test...",
299 dbc.Label("Number of Cores"),
302 id="cl-ctrl-core-all",
303 options=[{"label": "All", "value": "all"}, ],
318 id="row-ctrl-framesize",
321 dbc.Label("Frame Size"),
324 id="cl-ctrl-framesize-all",
325 options=[{"label": "All", "value": "all"}, ],
332 id="cl-ctrl-framesize",
340 id="row-ctrl-testtype",
343 dbc.Label("Test Type"),
346 id="cl-ctrl-testtype-all",
347 options=[{"label": "All", "value": "all"}, ],
354 id="cl-ctrl-testtype",
376 datetime.utcnow()-timedelta(days=180),
377 max_date_allowed=datetime.utcnow(),
378 initial_visible_month=datetime.utcnow(),
379 start_date=datetime.utcnow() - timedelta(days=180),
380 end_date=datetime.utcnow(),
381 display_format="D MMMM YY"
388 def _add_ctrl_shown(self) -> dbc.Row:
398 dbc.Label("Selected tests"),
413 children="Remove Selected",
418 id="btn-sel-remove-all",
419 children="Remove All",
424 id="btn-sel-display",
438 def callbacks(self, app):
441 Output("dd-ctrl-area", "options"),
442 Output("dd-ctrl-area", "disabled"),
443 Input("dd-ctrl-phy", "value"),
445 def _update_dd_area(phy):
454 {"label": self.spec_tbs[phy][v]["label"], "value": v}
455 for v in [v for v in self.spec_tbs[phy].keys()]
462 return options, disable
465 Output("dd-ctrl-test", "options"),
466 Output("dd-ctrl-test", "disabled"),
467 State("dd-ctrl-phy", "value"),
468 Input("dd-ctrl-area", "value"),
470 def _update_dd_test(phy, area):
479 {"label": v, "value": v}
480 for v in self.spec_tbs[phy][area]["test"]
487 return options, disable
490 # Output("row-ctrl-core", "style"),
491 Output("cl-ctrl-core", "options"),
492 # Output("row-ctrl-framesize", "style"),
493 Output("cl-ctrl-framesize", "options"),
494 # Output("row-ctrl-testtype", "style"),
495 Output("cl-ctrl-testtype", "options"),
496 # Output("btn-ctrl-add", "disabled"),
497 State("dd-ctrl-phy", "value"),
498 State("dd-ctrl-area", "value"),
499 Input("dd-ctrl-test", "value"),
501 def _update_btn_add(phy, area, test):
508 # core_style = {"display": "none"}
510 # framesize_style = {"display": "none"}
512 # testtype_style = {"display": "none"}
514 # add_disabled = True
515 if phy and area and test:
516 # core_style = {"display": "block"}
518 {"label": v, "value": v}
519 for v in self.spec_tbs[phy][area]["core"]
521 # framesize_style = {"display": "block"}
523 {"label": v, "value": v}
524 for v in self.spec_tbs[phy][area]["frame-size"]
526 # testtype_style = {"display": "block"}
528 {"label": v, "value": v}
529 for v in self.spec_tbs[phy][area]["test-type"]
531 # add_disabled = False
543 def _sync_checklists(opt, sel, all, id):
546 options = {v["value"] for v in opt}
547 input_id = callback_context.triggered[0]["prop_id"].split(".")[0]
549 all = ["all"] if set(sel) == options else list()
551 sel = list(options) if all else list()
555 Output("cl-ctrl-core", "value"),
556 Output("cl-ctrl-core-all", "value"),
557 State("cl-ctrl-core", "options"),
558 Input("cl-ctrl-core", "value"),
559 Input("cl-ctrl-core-all", "value"),
560 prevent_initial_call=True
562 def _sync_cl_core(opt, sel, all):
563 return _sync_checklists(opt, sel, all, "cl-ctrl-core")
566 Output("cl-ctrl-framesize", "value"),
567 Output("cl-ctrl-framesize-all", "value"),
568 State("cl-ctrl-framesize", "options"),
569 Input("cl-ctrl-framesize", "value"),
570 Input("cl-ctrl-framesize-all", "value"),
571 prevent_initial_call=True
573 def _sync_cl_framesize(opt, sel, all):
574 return _sync_checklists(opt, sel, all, "cl-ctrl-framesize")
577 Output("cl-ctrl-testtype", "value"),
578 Output("cl-ctrl-testtype-all", "value"),
579 State("cl-ctrl-testtype", "options"),
580 Input("cl-ctrl-testtype", "value"),
581 Input("cl-ctrl-testtype-all", "value"),
582 prevent_initial_call=True
584 def _sync_cl_testtype(opt, sel, all):
585 return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
588 # Output("graph-tput", "figure"),
589 # Output("graph-latency", "figure"),
590 # Output("div-tput", "style"),
591 # Output("div-latency", "style"),
592 # Output("div-lat-metadata", "style"),
593 # Output("div-download", "style"),
594 Output("selected-tests", "data"), # Store
595 Output("cl-selected", "options"), # User selection
596 Output("dd-ctrl-phy", "value"),
597 Output("dd-ctrl-area", "value"),
598 Output("dd-ctrl-test", "value"),
599 State("selected-tests", "data"), # Store
600 State("cl-selected", "value"),
601 State("dd-ctrl-phy", "value"),
602 State("dd-ctrl-area", "value"),
603 State("dd-ctrl-test", "value"),
604 State("cl-ctrl-core", "value"),
605 State("cl-ctrl-framesize", "value"),
606 State("cl-ctrl-testtype", "value"),
607 Input("btn-ctrl-add", "n_clicks"),
608 Input("btn-sel-display", "n_clicks"),
609 Input("btn-sel-remove", "n_clicks"),
610 Input("btn-sel-remove-all", "n_clicks"),
611 Input("dpr-period", "start_date"),
612 Input("dpr-period", "end_date"),
613 prevent_initial_call=True
615 def _process_list(store_sel, list_sel, phy, area, test, cores,
616 framesizes, testtypes, btn_add, btn_display, btn_remove,
617 btn_remove_all, d_start, d_end):
621 if not (btn_add or btn_display or btn_remove or btn_remove_all or \
626 # Display selected tests with checkboxes:
629 {"label": v["id"], "value": v["id"]} for v in store_sel
635 def __init__(self) -> None:
637 # "graph-tput-figure": no_update,
638 # "graph-lat-figure": no_update,
639 # "div-tput-style": no_update,
640 # "div-latency-style": no_update,
641 # "div-lat-metadata-style": no_update,
642 # "div-download-style": no_update,
643 "selected-tests-data": no_update,
644 "cl-selected-options": no_update,
645 "dd-ctrl-phy-value": no_update,
646 "dd-ctrl-area-value": no_update,
647 "dd-ctrl-test-value": no_update,
651 return tuple(self._output.values())
653 def set_values(self, kwargs: dict) -> None:
654 for key, val in kwargs.items():
655 if key in self._output:
656 self._output[key] = val
658 raise KeyError(f"The key {key} is not defined.")
661 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
663 d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
665 d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
667 output = RetunValue()
669 if trigger_id == "btn-ctrl-add":
670 # Add selected test to the list of tests in store:
671 if phy and area and test and cores and framesizes and testtypes:
672 if store_sel is None:
675 for framesize in framesizes:
676 for ttype in testtypes:
678 f"{phy.replace('af_xdp', 'af-xdp')}-"
680 f"{framesize.lower()}-"
685 if tid not in [itm["id"] for itm in store_sel]:
691 "framesize": framesize.lower(),
692 "core": core.lower(),
693 "testtype": ttype.lower()
696 "selected-tests-data": store_sel,
697 "cl-selected-options": _list_tests(),
698 "dd-ctrl-phy-value": None,
699 "dd-ctrl-area-value": None,
700 "dd-ctrl-test-value": None,
703 elif trigger_id in ("btn-sel-display", "dpr-period"):
704 fig_tput, fig_lat = graph_trending(
705 self.data, store_sel, self.layout, d_start, d_end
707 # output.set_values({
708 # "graph-tput-figure": \
709 # fig_tput if fig_tput else self.NO_GRAPH,
710 # "graph-lat-figure": \
711 # fig_lat if fig_lat else self.NO_GRAPH,
712 # "div-tput-style": \
713 # self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
714 # "div-latency-style": \
715 # self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
716 # "div-lat-metadata-style": \
717 # self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
718 # "div-download-style": \
719 # self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
721 elif trigger_id == "btn-sel-remove-all":
723 "selected-tests-data": list(),
724 "cl-selected-options": list()
726 elif trigger_id == "btn-sel-remove":
728 new_store_sel = list()
729 for item in store_sel:
730 if item["id"] not in list_sel:
731 new_store_sel.append(item)
732 store_sel = new_store_sel
734 fig_tput, fig_lat = graph_trending(
735 self.data, store_sel, self.layout, d_start, d_end
738 # "graph-tput-figure": \
739 # fig_tput if fig_tput else self.NO_GRAPH,
740 # "graph-lat-figure": \
741 # fig_lat if fig_lat else self.NO_GRAPH,
742 # "div-tput-style": \
743 # self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
744 # "div-latency-style": \
745 # self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
746 # "div-lat-metadata-style": \
747 # self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
748 # "div-download-style": \
749 # self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
750 "selected-tests-data": store_sel,
751 "cl-selected-options": _list_tests()
755 # "graph-tput-figure": self.NO_GRAPH,
756 # "graph-lat-figure": self.NO_GRAPH,
757 # "div-tput-style": self.STYLE_HIDEN,
758 # "div-latency-style": self.STYLE_HIDEN,
759 # "div-lat-metadata-style": self.STYLE_HIDEN,
760 # "div-download-style": self.STYLE_HIDEN,
761 "selected-tests-data": store_sel,
762 "cl-selected-options": _list_tests()
765 return output.value()
768 # Output("tput-metadata", "children"),
769 # Input("graph-tput", "clickData")
771 # def _show_tput_metadata(hover_data):
775 # raise PreventUpdate
777 # return hover_data["points"][0]["text"].replace("<br>", "\n")
780 # Output("graph-latency-hdrh", "figure"),
781 # Output("graph-latency-hdrh", "style"),
782 # Output("lat-metadata", "children"),
783 # Input("graph-latency", "clickData")
785 # def _show_latency_hdhr(hover_data):
789 # raise PreventUpdate
792 # hdrh_data = hover_data["points"][0].get("customdata", None)
794 # graph = graph_hdrh_latency(hdrh_data, self.layout)
799 # hover_data["points"][0]["text"].replace("<br>", "\n")
803 # Output("download-data", "data"),
804 # State("selected-tests", "data"),
805 # Input("btn-download-data", "n_clicks"),
806 # prevent_initial_call=True
808 # def _download_data(store_sel, n_clicks):
813 # raise PreventUpdate
815 # df = pd.DataFrame()
816 # for itm in store_sel:
817 # sel_data = select_trending_data(self.data, itm)
818 # if sel_data is None:
820 # df = pd.concat([df, sel_data], ignore_index=True)
822 # return dcc.send_data_frame(df.to_csv, "trending_data.csv")