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, callback
25 from dash.exceptions import PreventUpdate
26 from yaml import load, FullLoader, YAMLError
27 from datetime import datetime, timedelta
29 from ..data.data import Data
30 from .graphs import trending_tput
37 STYLE_HIDEN = {"display": "none"}
38 STYLE_BLOCK = {"display": "block"}
39 STYLE_INLINE ={"display": "inline-block", "width": "50%"}
40 NO_GRAPH = {"data": [], "layout": {}, "frames": []}
42 def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
49 self._html_layout_file = html_layout_file
50 self._spec_file = spec_file
51 self._graph_layout_file = graph_layout_file
52 self._data_spec_file = data_spec_file
56 data_spec_file=self._data_spec_file,
61 data_spec_file=self._data_spec_file,
63 ).read_trending_ndrpdr()
65 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
68 self._html_layout = ""
70 self._graph_layout = None
73 with open(self._html_layout_file, "r") as file_read:
74 self._html_layout = file_read.read()
75 except IOError as err:
77 f"Not possible to open the file {self._html_layout_file}\n{err}"
81 with open(self._spec_file, "r") as file_read:
82 self._spec_tbs = load(file_read, Loader=FullLoader)
83 except IOError as err:
85 f"Not possible to open the file {self._spec_file,}\n{err}"
87 except YAMLError as err:
89 f"An error occurred while parsing the specification file "
90 f"{self._spec_file,}\n"
95 with open(self._graph_layout_file, "r") as file_read:
96 self._graph_layout = load(file_read, Loader=FullLoader)
97 except IOError as err:
99 f"Not possible to open the file {self._graph_layout_file}\n"
102 except YAMLError as err:
104 f"An error occurred while parsing the specification file "
105 f"{self._graph_layout_file}\n"
110 if self._app is not None and hasattr(self, 'callbacks'):
111 self.callbacks(self._app)
114 def html_layout(self):
115 return self._html_layout
119 return self._spec_tbs
127 return self._graph_layout
129 def add_content(self):
132 if self.html_layout and self.spec_tbs:
136 dcc.Store(id="selected-tests"),
137 self._add_ctrl_div(),
138 self._add_plotting_div()
144 children="An Error Occured."
147 def _add_ctrl_div(self):
148 """Add div with controls. It is placed on the left side.
154 id="div-controls-tabs",
156 self._add_ctrl_select(),
157 self._add_ctrl_shown()
162 "display": "inline-block",
168 def _add_plotting_div(self):
169 """Add div with plots and tables. It is placed on the right side.
172 id="div-plotting-area",
179 style=self.STYLE_HIDEN
183 style=self.STYLE_HIDEN
191 id="div-tput-metadata",
196 Click on data points in the graph.
202 style=self.STYLE_HIDEN
205 id="div-latency-metadata",
210 Click on data points in the graph.
213 id="latency-metadata"
216 style=self.STYLE_HIDEN
222 "vertical-align": "top",
223 "display": "inline-block",
229 def _add_ctrl_shown(self):
235 html.H5("Selected tests"),
237 id="container-selected-tests",
242 labelStyle={"display": "block"}
246 children="Remove Selected",
250 id="btn-sel-display",
259 def _add_ctrl_select(self):
263 id="div-ctrl-select",
265 html.H5("Physical Test Bed Topology, NIC and Driver"),
268 placeholder="Select a Physical Test Bed Topology...",
272 {"label": k, "value": k} for k in self.spec_tbs.keys()
278 placeholder="Select an Area...",
286 placeholder="Select a Test...",
294 html.H5("Number of Cores"),
296 id="cl-ctrl-core-all",
297 options=[{"label": "All", "value": "all"}, ],
298 labelStyle={"display": "inline-block"}
302 labelStyle={"display": "inline-block"}
305 style={"display": "none"}
308 id="div-ctrl-framesize",
310 html.H5("Frame Size"),
312 id="cl-ctrl-framesize-all",
313 options=[{"label": "All", "value": "all"}, ],
314 labelStyle={"display": "inline-block"}
317 id="cl-ctrl-framesize",
318 labelStyle={"display": "inline-block"}
321 style={"display": "none"}
324 id="div-ctrl-testtype",
326 html.H5("Test Type"),
328 id="cl-ctrl-testtype-all",
329 options=[{"label": "All", "value": "all"}, ],
330 labelStyle={"display": "inline-block"}
333 id="cl-ctrl-testtype",
334 labelStyle={"display": "inline-block"}
337 style={"display": "none"}
347 min_date_allowed=datetime.utcnow() - timedelta(days=180),
348 max_date_allowed=datetime.utcnow(),
349 initial_visible_month=datetime.utcnow(),
350 start_date=datetime.utcnow() - timedelta(days=180),
351 end_date=datetime.utcnow(),
352 display_format="D MMMM YY"
357 def callbacks(self, app):
360 Output("dd-ctrl-area", "options"),
361 Output("dd-ctrl-area", "disabled"),
362 Input("dd-ctrl-phy", "value"),
364 def _update_dd_area(phy):
373 {"label": self.spec_tbs[phy][v]["label"], "value": v}
374 for v in [v for v in self.spec_tbs[phy].keys()]
381 return options, disable
384 Output("dd-ctrl-test", "options"),
385 Output("dd-ctrl-test", "disabled"),
386 State("dd-ctrl-phy", "value"),
387 Input("dd-ctrl-area", "value"),
389 def _update_dd_test(phy, area):
398 {"label": v, "value": v}
399 for v in self.spec_tbs[phy][area]["test"]
406 return options, disable
409 Output("div-ctrl-core", "style"),
410 Output("cl-ctrl-core", "options"),
411 Output("div-ctrl-framesize", "style"),
412 Output("cl-ctrl-framesize", "options"),
413 Output("div-ctrl-testtype", "style"),
414 Output("cl-ctrl-testtype", "options"),
415 Output("btn-ctrl-add", "disabled"),
416 State("dd-ctrl-phy", "value"),
417 State("dd-ctrl-area", "value"),
418 Input("dd-ctrl-test", "value"),
420 def _update_btn_add(phy, area, test):
427 core_style = {"display": "none"}
429 framesize_style = {"display": "none"}
431 testtype_style = {"display": "none"}
434 if phy and area and test:
435 core_style = {"display": "block"}
437 {"label": v, "value": v}
438 for v in self.spec_tbs[phy][area]["core"]
440 framesize_style = {"display": "block"}
442 {"label": v, "value": v}
443 for v in self.spec_tbs[phy][area]["frame-size"]
445 testtype_style = {"display": "block"}
447 {"label": v, "value": v}
448 for v in self.spec_tbs[phy][area]["test-type"]
453 core_style, core_opts,
454 framesize_style, framesize_opts,
455 testtype_style, testtype_opts,
459 def _sync_checklists(opt, sel, all, id):
462 options = {v["value"] for v in opt}
463 input_id = callback_context.triggered[0]["prop_id"].split(".")[0]
465 all = ["all"] if set(sel) == options else list()
467 sel = list(options) if all else list()
471 Output("cl-ctrl-core", "value"),
472 Output("cl-ctrl-core-all", "value"),
473 State("cl-ctrl-core", "options"),
474 Input("cl-ctrl-core", "value"),
475 Input("cl-ctrl-core-all", "value"),
476 prevent_initial_call=True
478 def _sync_cl_core(opt, sel, all):
479 return _sync_checklists(opt, sel, all, "cl-ctrl-core")
482 Output("cl-ctrl-framesize", "value"),
483 Output("cl-ctrl-framesize-all", "value"),
484 State("cl-ctrl-framesize", "options"),
485 Input("cl-ctrl-framesize", "value"),
486 Input("cl-ctrl-framesize-all", "value"),
487 prevent_initial_call=True
489 def _sync_cl_framesize(opt, sel, all):
490 return _sync_checklists(opt, sel, all, "cl-ctrl-framesize")
493 Output("cl-ctrl-testtype", "value"),
494 Output("cl-ctrl-testtype-all", "value"),
495 State("cl-ctrl-testtype", "options"),
496 Input("cl-ctrl-testtype", "value"),
497 Input("cl-ctrl-testtype-all", "value"),
498 prevent_initial_call=True
500 def _sync_cl_testtype(opt, sel, all):
501 return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
504 Output("graph-tput", "figure"),
505 Output("graph-tput", "style"),
506 Output("div-tput-metadata", "style"),
507 Output("graph-latency", "figure"),
508 Output("graph-latency", "style"),
509 Output("div-latency-metadata", "style"),
510 Output("selected-tests", "data"), # Store
511 Output("cl-selected", "options"), # User selection
512 Output("dd-ctrl-phy", "value"),
513 Output("dd-ctrl-area", "value"),
514 Output("dd-ctrl-test", "value"),
515 State("selected-tests", "data"), # Store
516 State("cl-selected", "value"),
517 State("dd-ctrl-phy", "value"),
518 State("dd-ctrl-area", "value"),
519 State("dd-ctrl-test", "value"),
520 State("cl-ctrl-core", "value"),
521 State("cl-ctrl-framesize", "value"),
522 State("cl-ctrl-testtype", "value"),
523 Input("btn-ctrl-add", "n_clicks"),
524 Input("btn-sel-display", "n_clicks"),
525 Input("btn-sel-remove", "n_clicks"),
526 Input("dpr-period", "start_date"),
527 Input("dpr-period", "end_date"),
528 prevent_initial_call=True
530 def _process_list(store_sel, list_sel, phy, area, test, cores,
531 framesizes, testtypes, btn_add, btn_display, btn_remove,
536 if not (btn_add or btn_display or btn_remove or d_start or d_end):
540 # Display selected tests with checkboxes:
543 {"label": v["id"], "value": v["id"]} for v in store_sel
549 def __init__(self) -> None:
551 "graph-tput-figure": no_update,
552 "graph-tput-style": no_update,
553 "div-tput-metadata-style": no_update,
554 "graph-lat-figure": no_update,
555 "graph-lat-style": no_update,
556 "div-lat-metadata-style": no_update,
557 "selected-tests-data": no_update,
558 "cl-selected-options": no_update,
559 "dd-ctrl-phy-value": no_update,
560 "dd-ctrl-area-value": no_update,
561 "dd-ctrl-test-value": no_update,
565 return tuple(self._output.values())
567 def set_values(self, kwargs: dict) -> None:
568 for key, val in kwargs.items():
569 if key in self._output:
570 self._output[key] = val
572 raise KeyError(f"The key {key} is not defined.")
575 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
577 d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
579 d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
581 output = RetunValue()
583 if trigger_id == "btn-ctrl-add":
584 # Add selected test to the list of tests in store:
585 if phy and area and test and cores and framesizes and testtypes:
586 if store_sel is None:
589 for framesize in framesizes:
590 for ttype in testtypes:
592 f"{phy.replace('af_xdp', 'af-xdp')}-"
594 f"{framesize.lower()}-"
599 if tid not in [itm["id"] for itm in store_sel]:
605 "framesize": framesize.lower(),
606 "core": core.lower(),
607 "testtype": ttype.lower()
610 "selected-tests-data": store_sel,
611 "cl-selected-options": _list_tests(),
612 "dd-ctrl-phy-value": None,
613 "dd-ctrl-area-value": None,
614 "dd-ctrl-test-value": None,
617 elif trigger_id in ("btn-sel-display", "dpr-period"):
618 fig_tput, fig_lat = trending_tput(
619 self.data, store_sel, self.layout, d_start, d_end
622 "graph-tput-figure": \
623 fig_tput if fig_tput else self.NO_GRAPH,
624 "graph-tput-style": \
625 self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
626 "div-tput-metadata-style": \
627 self.STYLE_INLINE if fig_tput else self.STYLE_HIDEN,
628 "graph-lat-figure": \
629 fig_lat if fig_lat else self.NO_GRAPH,
631 self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
632 "div-lat-metadata-style": \
633 self.STYLE_INLINE if fig_lat else self.STYLE_HIDEN
636 elif trigger_id == "btn-sel-remove":
638 new_store_sel = list()
639 for item in store_sel:
640 if item["id"] not in list_sel:
641 new_store_sel.append(item)
642 store_sel = new_store_sel
644 fig_tput, fig_lat = trending_tput(
645 self.data, store_sel, self.layout, d_start, d_end
648 "graph-tput-figure": \
649 fig_tput if fig_tput else self.NO_GRAPH,
650 "graph-tput-style": \
651 self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
652 "div-tput-metadata-style": \
653 self.STYLE_INLINE if fig_tput else self.STYLE_HIDEN,
654 "graph-lat-figure": \
655 fig_lat if fig_lat else self.NO_GRAPH,
657 self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
658 "div-lat-metadata-style": \
659 self.STYLE_INLINE if fig_lat else self.STYLE_HIDEN,
660 "selected-tests-data": store_sel,
661 "cl-selected-options": _list_tests()
665 "graph-tput-figure": self.NO_GRAPH,
666 "graph-tput-style": self.STYLE_HIDEN,
667 "div-tput-metadata-style": self.STYLE_HIDEN,
668 "graph-lat-figure": self.NO_GRAPH,
669 "graph-lat-style": self.STYLE_HIDEN,
670 "div-lat-metadata-style": self.STYLE_HIDEN,
671 "selected-tests-data": store_sel,
672 "cl-selected-options": _list_tests()
675 return output.value()
678 Output("tput-metadata", "children"),
679 Input("graph-tput", "clickData")
681 def _show_tput_metadata(hover_data):
684 return json.dumps(hover_data, indent=2)
687 Output("latency-metadata", "children"),
688 Input("graph-latency", "clickData")
690 def _show_latency_metadata(hover_data):
693 return json.dumps(hover_data, indent=2)