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",
232 def _add_ctrl_panel(self) -> dbc.Row:
239 dbc.Label("Physical Test Bed Topology, NIC and Driver"),
242 placeholder="Select a Physical Test Bed Topology...",
246 {"label": k, "value": k} for k in self.spec_tbs.keys()
252 placeholder="Select an Area...",
260 placeholder="Select a Test...",
269 dbc.Label("Number of Cores"),
271 id="cl-ctrl-core-all",
272 options=[{"label": "All", "value": "all"}, ],
284 id="row-ctrl-framesize",
287 dbc.Label("Frame Size"),
289 id="cl-ctrl-framesize-all",
290 options=[{"label": "All", "value": "all"}, ],
295 id="cl-ctrl-framesize",
302 id="row-ctrl-testtype",
305 dbc.Label("Test Type"),
307 id="cl-ctrl-testtype-all",
308 options=[{"label": "All", "value": "all"}, ],
313 id="cl-ctrl-testtype",
334 datetime.utcnow()-timedelta(days=180),
335 max_date_allowed=datetime.utcnow(),
336 initial_visible_month=datetime.utcnow(),
337 start_date=datetime.utcnow() - timedelta(days=180),
338 end_date=datetime.utcnow(),
339 display_format="D MMMM YY"
346 def _add_ctrl_shown(self) -> dbc.Row:
356 dbc.Label("Selected tests"),
371 children="Remove Selected",
376 id="btn-sel-remove-all",
377 children="Remove All",
382 id="btn-sel-display",
396 def callbacks(self, app):
399 Output("dd-ctrl-area", "options"),
400 Output("dd-ctrl-area", "disabled"),
401 Input("dd-ctrl-phy", "value"),
403 def _update_dd_area(phy):
412 {"label": self.spec_tbs[phy][v]["label"], "value": v}
413 for v in [v for v in self.spec_tbs[phy].keys()]
420 return options, disable
423 Output("dd-ctrl-test", "options"),
424 Output("dd-ctrl-test", "disabled"),
425 State("dd-ctrl-phy", "value"),
426 Input("dd-ctrl-area", "value"),
428 def _update_dd_test(phy, area):
437 {"label": v, "value": v}
438 for v in self.spec_tbs[phy][area]["test"]
445 return options, disable
448 # Output("row-ctrl-core", "style"),
449 Output("cl-ctrl-core", "options"),
450 # Output("row-ctrl-framesize", "style"),
451 Output("cl-ctrl-framesize", "options"),
452 # Output("row-ctrl-testtype", "style"),
453 Output("cl-ctrl-testtype", "options"),
454 # Output("btn-ctrl-add", "disabled"),
455 State("dd-ctrl-phy", "value"),
456 State("dd-ctrl-area", "value"),
457 Input("dd-ctrl-test", "value"),
459 def _update_btn_add(phy, area, test):
466 # core_style = {"display": "none"}
468 # framesize_style = {"display": "none"}
470 # testtype_style = {"display": "none"}
472 # add_disabled = True
473 if phy and area and test:
474 # core_style = {"display": "block"}
476 {"label": v, "value": v}
477 for v in self.spec_tbs[phy][area]["core"]
479 # framesize_style = {"display": "block"}
481 {"label": v, "value": v}
482 for v in self.spec_tbs[phy][area]["frame-size"]
484 # testtype_style = {"display": "block"}
486 {"label": v, "value": v}
487 for v in self.spec_tbs[phy][area]["test-type"]
489 # add_disabled = False
501 def _sync_checklists(opt, sel, all, id):
504 options = {v["value"] for v in opt}
505 input_id = callback_context.triggered[0]["prop_id"].split(".")[0]
507 all = ["all"] if set(sel) == options else list()
509 sel = list(options) if all else list()
513 Output("cl-ctrl-core", "value"),
514 Output("cl-ctrl-core-all", "value"),
515 State("cl-ctrl-core", "options"),
516 Input("cl-ctrl-core", "value"),
517 Input("cl-ctrl-core-all", "value"),
518 prevent_initial_call=True
520 def _sync_cl_core(opt, sel, all):
521 return _sync_checklists(opt, sel, all, "cl-ctrl-core")
524 Output("cl-ctrl-framesize", "value"),
525 Output("cl-ctrl-framesize-all", "value"),
526 State("cl-ctrl-framesize", "options"),
527 Input("cl-ctrl-framesize", "value"),
528 Input("cl-ctrl-framesize-all", "value"),
529 prevent_initial_call=True
531 def _sync_cl_framesize(opt, sel, all):
532 return _sync_checklists(opt, sel, all, "cl-ctrl-framesize")
535 Output("cl-ctrl-testtype", "value"),
536 Output("cl-ctrl-testtype-all", "value"),
537 State("cl-ctrl-testtype", "options"),
538 Input("cl-ctrl-testtype", "value"),
539 Input("cl-ctrl-testtype-all", "value"),
540 prevent_initial_call=True
542 def _sync_cl_testtype(opt, sel, all):
543 return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
546 # Output("graph-tput", "figure"),
547 # Output("graph-latency", "figure"),
548 # Output("div-tput", "style"),
549 # Output("div-latency", "style"),
550 # Output("div-lat-metadata", "style"),
551 # Output("div-download", "style"),
552 Output("selected-tests", "data"), # Store
553 Output("cl-selected", "options"), # User selection
554 Output("dd-ctrl-phy", "value"),
555 Output("dd-ctrl-area", "value"),
556 Output("dd-ctrl-test", "value"),
557 State("selected-tests", "data"), # Store
558 State("cl-selected", "value"),
559 State("dd-ctrl-phy", "value"),
560 State("dd-ctrl-area", "value"),
561 State("dd-ctrl-test", "value"),
562 State("cl-ctrl-core", "value"),
563 State("cl-ctrl-framesize", "value"),
564 State("cl-ctrl-testtype", "value"),
565 Input("btn-ctrl-add", "n_clicks"),
566 Input("btn-sel-display", "n_clicks"),
567 Input("btn-sel-remove", "n_clicks"),
568 Input("btn-sel-remove-all", "n_clicks"),
569 Input("dpr-period", "start_date"),
570 Input("dpr-period", "end_date"),
571 prevent_initial_call=True
573 def _process_list(store_sel, list_sel, phy, area, test, cores,
574 framesizes, testtypes, btn_add, btn_display, btn_remove,
575 btn_remove_all, d_start, d_end):
579 if not (btn_add or btn_display or btn_remove or btn_remove_all or \
584 # Display selected tests with checkboxes:
587 {"label": v["id"], "value": v["id"]} for v in store_sel
593 def __init__(self) -> None:
595 # "graph-tput-figure": no_update,
596 # "graph-lat-figure": no_update,
597 # "div-tput-style": no_update,
598 # "div-latency-style": no_update,
599 # "div-lat-metadata-style": no_update,
600 # "div-download-style": no_update,
601 "selected-tests-data": no_update,
602 "cl-selected-options": no_update,
603 "dd-ctrl-phy-value": no_update,
604 "dd-ctrl-area-value": no_update,
605 "dd-ctrl-test-value": no_update,
609 return tuple(self._output.values())
611 def set_values(self, kwargs: dict) -> None:
612 for key, val in kwargs.items():
613 if key in self._output:
614 self._output[key] = val
616 raise KeyError(f"The key {key} is not defined.")
619 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
621 d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
623 d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
625 output = RetunValue()
627 if trigger_id == "btn-ctrl-add":
628 # Add selected test to the list of tests in store:
629 if phy and area and test and cores and framesizes and testtypes:
630 if store_sel is None:
633 for framesize in framesizes:
634 for ttype in testtypes:
636 f"{phy.replace('af_xdp', 'af-xdp')}-"
638 f"{framesize.lower()}-"
643 if tid not in [itm["id"] for itm in store_sel]:
649 "framesize": framesize.lower(),
650 "core": core.lower(),
651 "testtype": ttype.lower()
654 "selected-tests-data": store_sel,
655 "cl-selected-options": _list_tests(),
656 "dd-ctrl-phy-value": None,
657 "dd-ctrl-area-value": None,
658 "dd-ctrl-test-value": None,
661 elif trigger_id in ("btn-sel-display", "dpr-period"):
662 fig_tput, fig_lat = graph_trending(
663 self.data, store_sel, self.layout, d_start, d_end
665 # output.set_values({
666 # "graph-tput-figure": \
667 # fig_tput if fig_tput else self.NO_GRAPH,
668 # "graph-lat-figure": \
669 # fig_lat if fig_lat else self.NO_GRAPH,
670 # "div-tput-style": \
671 # self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
672 # "div-latency-style": \
673 # self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
674 # "div-lat-metadata-style": \
675 # self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
676 # "div-download-style": \
677 # self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
679 elif trigger_id == "btn-sel-remove-all":
681 "selected-tests-data": list(),
682 "cl-selected-options": list()
684 elif trigger_id == "btn-sel-remove":
686 new_store_sel = list()
687 for item in store_sel:
688 if item["id"] not in list_sel:
689 new_store_sel.append(item)
690 store_sel = new_store_sel
692 fig_tput, fig_lat = graph_trending(
693 self.data, store_sel, self.layout, d_start, d_end
696 # "graph-tput-figure": \
697 # fig_tput if fig_tput else self.NO_GRAPH,
698 # "graph-lat-figure": \
699 # fig_lat if fig_lat else self.NO_GRAPH,
700 # "div-tput-style": \
701 # self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
702 # "div-latency-style": \
703 # self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
704 # "div-lat-metadata-style": \
705 # self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
706 # "div-download-style": \
707 # self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
708 "selected-tests-data": store_sel,
709 "cl-selected-options": _list_tests()
713 # "graph-tput-figure": self.NO_GRAPH,
714 # "graph-lat-figure": self.NO_GRAPH,
715 # "div-tput-style": self.STYLE_HIDEN,
716 # "div-latency-style": self.STYLE_HIDEN,
717 # "div-lat-metadata-style": self.STYLE_HIDEN,
718 # "div-download-style": self.STYLE_HIDEN,
719 "selected-tests-data": store_sel,
720 "cl-selected-options": _list_tests()
723 return output.value()
726 # Output("tput-metadata", "children"),
727 # Input("graph-tput", "clickData")
729 # def _show_tput_metadata(hover_data):
733 # raise PreventUpdate
735 # return hover_data["points"][0]["text"].replace("<br>", "\n")
738 # Output("graph-latency-hdrh", "figure"),
739 # Output("graph-latency-hdrh", "style"),
740 # Output("lat-metadata", "children"),
741 # Input("graph-latency", "clickData")
743 # def _show_latency_hdhr(hover_data):
747 # raise PreventUpdate
750 # hdrh_data = hover_data["points"][0].get("customdata", None)
752 # graph = graph_hdrh_latency(hdrh_data, self.layout)
757 # hover_data["points"][0]["text"].replace("<br>", "\n")
761 # Output("download-data", "data"),
762 # State("selected-tests", "data"),
763 # Input("btn-download-data", "n_clicks"),
764 # prevent_initial_call=True
766 # def _download_data(store_sel, n_clicks):
771 # raise PreventUpdate
773 # df = pd.DataFrame()
774 # for itm in store_sel:
775 # sel_data = select_trending_data(self.data, itm)
776 # if sel_data is None:
778 # df = pd.concat([df, sel_data], ignore_index=True)
780 # return dcc.send_data_frame(df.to_csv, "trending_data.csv")