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.
18 import plotly.graph_objects as go
22 from dash import callback_context, no_update
23 from dash import Input, Output, State, callback
24 from dash.exceptions import PreventUpdate
25 from yaml import load, FullLoader, YAMLError
26 from datetime import datetime, timedelta
28 from pprint import pformat
30 from .data import read_data
37 def __init__(self, app, html_layout_file, spec_file, graph_layout_file):
43 self._html_layout_file = html_layout_file
44 self._spec_file = spec_file
45 self._graph_layout_file = graph_layout_file
48 self._data = read_data()
51 self._html_layout = ""
53 self._graph_layout = None
56 with open(self._html_layout_file, "r") as file_read:
57 self._html_layout = file_read.read()
58 except IOError as err:
60 f"Not possible to open the file {self._html_layout_file}\n{err}"
64 with open(self._spec_file, "r") as file_read:
65 self._spec_tbs = load(file_read, Loader=FullLoader)
66 except IOError as err:
68 f"Not possible to open the file {self._spec_file,}\n{err}"
70 except YAMLError as err:
72 f"An error occurred while parsing the specification file "
73 f"{self._spec_file,}\n"
78 with open(self._graph_layout_file, "r") as file_read:
79 self._graph_layout = load(file_read, Loader=FullLoader)
80 except IOError as err:
82 f"Not possible to open the file {self._graph_layout_file}\n"
85 except YAMLError as err:
87 f"An error occurred while parsing the specification file "
88 f"{self._graph_layout_file}\n"
93 if self._app is not None and hasattr(self, 'callbacks'):
94 self.callbacks(self._app)
97 def html_layout(self):
98 return self._html_layout
102 return self._spec_tbs
108 def add_content(self):
111 if self.html_layout and self.spec_tbs:
115 dcc.Store(id="selected-tests"),
116 self._add_ctrl_div(),
117 self._add_plotting_div()
123 children="An Error Occured."
126 def _add_ctrl_div(self):
127 """Add div with controls. It is placed on the left side.
133 id="div-controls-tabs",
135 self._add_ctrl_select(),
136 self._add_ctrl_shown()
141 "display": "inline-block",
147 def _add_plotting_div(self):
148 """Add div with plots and tables. It is placed on the right side.
151 id="div-plotting-area",
164 "vertical-align": "top",
171 def _add_ctrl_shown(self):
177 html.H5("Selected tests"),
179 id="container-selected-tests",
184 labelStyle={"display": "block"}
188 children="Remove Selected",
192 id="btn-sel-display",
198 # Debug output, TODO: Remove
199 html.H5("Debug output"),
200 html.Pre(id="div-ctrl-info")
204 def _add_ctrl_select(self):
208 id="div-ctrl-select",
210 html.H5("Physical Test Bed Topology, NIC and Driver"),
213 placeholder="Select a Physical Test Bed Topology...",
217 {"label": k, "value": k} for k in self.spec_tbs.keys()
223 placeholder="Select an Area...",
231 placeholder="Select a Test...",
239 html.H5("Number of Cores"),
241 id="cl-ctrl-core-all",
242 options=[{"label": "All", "value": "all"}, ],
243 labelStyle={"display": "inline-block"}
247 labelStyle={"display": "inline-block"}
250 style={"display": "none"}
253 id="div-ctrl-framesize",
255 html.H5("Frame Size"),
257 id="cl-ctrl-framesize-all",
258 options=[{"label": "All", "value": "all"}, ],
259 labelStyle={"display": "inline-block"}
262 id="cl-ctrl-framesize",
263 labelStyle={"display": "inline-block"}
266 style={"display": "none"}
269 id="div-ctrl-testtype",
271 html.H5("Test Type"),
273 id="cl-ctrl-testtype-all",
274 options=[{"label": "All", "value": "all"}, ],
275 labelStyle={"display": "inline-block"}
278 id="cl-ctrl-testtype",
279 labelStyle={"display": "inline-block"}
282 style={"display": "none"}
292 min_date_allowed=datetime(2021, 1, 1),
293 max_date_allowed=datetime.utcnow(),
294 initial_visible_month=datetime.utcnow(),
295 start_date=datetime.utcnow() - timedelta(days=180),
296 end_date=datetime.utcnow()
301 def callbacks(self, app):
304 Output("dd-ctrl-area", "options"),
305 Output("dd-ctrl-area", "disabled"),
306 Input("dd-ctrl-phy", "value"),
308 def _update_dd_area(phy):
317 {"label": self.spec_tbs[phy][v]["label"], "value": v}
318 for v in [v for v in self.spec_tbs[phy].keys()]
325 return options, disable
328 Output("dd-ctrl-test", "options"),
329 Output("dd-ctrl-test", "disabled"),
330 State("dd-ctrl-phy", "value"),
331 Input("dd-ctrl-area", "value"),
333 def _update_dd_test(phy, area):
342 {"label": v, "value": v}
343 for v in self.spec_tbs[phy][area]["test"]
350 return options, disable
353 Output("div-ctrl-core", "style"),
354 Output("cl-ctrl-core", "options"),
355 Output("div-ctrl-framesize", "style"),
356 Output("cl-ctrl-framesize", "options"),
357 Output("div-ctrl-testtype", "style"),
358 Output("cl-ctrl-testtype", "options"),
359 Output("btn-ctrl-add", "disabled"),
360 State("dd-ctrl-phy", "value"),
361 State("dd-ctrl-area", "value"),
362 Input("dd-ctrl-test", "value"),
364 def _update_btn_add(phy, area, test):
371 core_style = {"display": "none"}
373 framesize_style = {"display": "none"}
375 testtype_style = {"display": "none"}
378 if phy and area and test:
379 core_style = {"display": "block"}
381 {"label": v, "value": v}
382 for v in self.spec_tbs[phy][area]["core"]
384 framesize_style = {"display": "block"}
386 {"label": v, "value": v}
387 for v in self.spec_tbs[phy][area]["frame-size"]
389 testtype_style = {"display": "block"}
391 {"label": v, "value": v}
392 for v in self.spec_tbs[phy][area]["test-type"]
397 core_style, core_opts,
398 framesize_style, framesize_opts,
399 testtype_style, testtype_opts,
403 def _sync_checklists(opt, sel, all, id):
406 options = {v["value"] for v in opt}
407 input_id = callback_context.triggered[0]["prop_id"].split(".")[0]
409 all = ["all"] if set(sel) == options else list()
411 sel = list(options) if all else list()
415 Output("cl-ctrl-core", "value"),
416 Output("cl-ctrl-core-all", "value"),
417 State("cl-ctrl-core", "options"),
418 Input("cl-ctrl-core", "value"),
419 Input("cl-ctrl-core-all", "value"),
420 prevent_initial_call=True
422 def _sync_cl_core(opt, sel, all):
423 return _sync_checklists(opt, sel, all, "cl-ctrl-core")
426 Output("cl-ctrl-framesize", "value"),
427 Output("cl-ctrl-framesize-all", "value"),
428 State("cl-ctrl-framesize", "options"),
429 Input("cl-ctrl-framesize", "value"),
430 Input("cl-ctrl-framesize-all", "value"),
431 prevent_initial_call=True
433 def _sync_cl_framesize(opt, sel, all):
434 return _sync_checklists(opt, sel, all, "cl-ctrl-framesize")
437 Output("cl-ctrl-testtype", "value"),
438 Output("cl-ctrl-testtype-all", "value"),
439 State("cl-ctrl-testtype", "options"),
440 Input("cl-ctrl-testtype", "value"),
441 Input("cl-ctrl-testtype-all", "value"),
442 prevent_initial_call=True
444 def _sync_cl_testtype(opt, sel, all):
445 return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
448 Output("graph", "figure"),
449 Output("div-ctrl-info", "children"), # Debug output TODO: Remove
450 Output("selected-tests", "data"), # Store
451 Output("cl-selected", "options"), # User selection
452 Output("dd-ctrl-phy", "value"),
453 Output("dd-ctrl-area", "value"),
454 Output("dd-ctrl-test", "value"),
455 Output("div-plotting-area", "style"),
456 State("selected-tests", "data"), # Store
457 State("cl-selected", "value"),
458 State("dd-ctrl-phy", "value"),
459 State("dd-ctrl-area", "value"),
460 State("dd-ctrl-test", "value"),
461 State("cl-ctrl-core", "value"),
462 State("cl-ctrl-framesize", "value"),
463 State("cl-ctrl-testtype", "value"),
464 Input("btn-ctrl-add", "n_clicks"),
465 Input("btn-sel-display", "n_clicks"),
466 Input("btn-sel-remove", "n_clicks"),
467 Input("dpr-period", "start_date"),
468 Input("dpr-period", "end_date"),
469 prevent_initial_call=True
471 def _process_list(store_sel, list_sel, phy, area, test, cores,
472 framesizes, testtypes, btn_add, btn_display, btn_remove,
477 if not (btn_add or btn_display or btn_remove or d_start or d_end):
481 # Display selected tests with checkboxes:
484 {"label": v["id"], "value": v["id"]} for v in store_sel
489 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
492 int(d_start[0:4]), int(d_start[5:7]), int(d_start[8:10])
495 int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10])
498 if trigger_id == "btn-ctrl-add":
499 # Add selected test to the list of tests in store:
500 if phy and area and test and cores and framesizes and testtypes:
502 # TODO: Add validation
504 if store_sel is None:
508 for framesize in framesizes:
509 for ttype in testtypes:
513 f"{framesize.lower()}-"
518 if tid not in [itm["id"] for itm in store_sel]:
524 "framesize": framesize.lower(),
525 "core": core.lower(),
526 "testtype": ttype.lower()
528 return (no_update, no_update, store_sel, _list_tests(), None,
529 None, None, no_update)
531 elif trigger_id in ("btn-sel-display", "dpr-period"):
532 fig, style = _update_graph(store_sel, d_start, d_end)
533 return (fig, pformat(store_sel), no_update, no_update,
534 no_update, no_update, no_update, style)
536 elif trigger_id == "btn-sel-remove":
538 new_store_sel = list()
539 for item in store_sel:
540 if item["id"] not in list_sel:
541 new_store_sel.append(item)
542 store_sel = new_store_sel
543 fig, style = _update_graph(store_sel, d_start, d_end)
544 return (fig, pformat(store_sel), store_sel, _list_tests(),
545 no_update, no_update, no_update, style)
547 def _update_graph(sel, start, end):
552 return no_update, no_update
554 def _is_selected(label, sel):
556 phy = itm["phy"].split("-")
558 topo, arch, nic, drv = phy
563 if drv != "dpdk" and drv not in label:
565 if itm["test"] not in label:
567 if itm["framesize"] not in label:
569 if itm["core"] not in label:
571 if itm["testtype"] not in label:
574 f"{itm['phy']}-{itm['framesize']}-{itm['core']}-"
575 f"{itm['test']}-{itm['testtype']}"
581 "vertical-align": "top",
582 "display": "inline-block",
588 dates = self.data.iloc[[0], 1:].values.flatten().tolist()[::-1]
591 int(date[0:4]), int(date[4:6]), int(date[6:8]),
592 int(date[9:11]), int(date[12:])
596 date for date in x_data if date >= start and date <= end
598 vpp = self.data.iloc[[1], 1:].values.flatten().tolist()[::-1]
599 csit = list(self.data.columns[1:])[::-1]
600 labels = list(self.data["Build Number:"][3:])
601 for i in range(3, len(self.data)):
602 name = _is_selected(labels[i-3], sel)
606 float(v) / 1e6 for v in \
607 self.data.iloc[[i], 1:].values.flatten().tolist()[::-1]
610 for x_idx, x_itm in enumerate(x_data):
613 f"average [Mpps]: {y_data[x_idx]}<br>"
614 f"vpp-ref: {vpp[x_idx]}<br>"
615 f"csit-ref: {csit[x_idx]}"
622 mode="markers+lines",
624 hoverinfo=u"text+name"
627 layout = self._graph_layout.get("plot-trending", dict())
628 fig.update_layout(layout)