feat(uti): Generate structure of tests from data for trending
[csit.git] / resources / tools / dash / app / pal / trending / layout.py
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:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
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.
13
14 """Plotly Dash HTML layout override.
15 """
16
17 import pandas as pd
18 import dash_bootstrap_components as dbc
19
20 from flask import Flask
21 from dash import dcc
22 from dash import html
23 from dash import callback_context, no_update, ALL
24 from dash import Input, Output, State
25 from dash.exceptions import PreventUpdate
26 from yaml import load, FullLoader, YAMLError
27 from datetime import datetime, timedelta
28 from copy import deepcopy
29 from json import loads, JSONDecodeError
30
31 from ..data.data import Data
32 from .graphs import graph_trending, graph_hdrh_latency, \
33     select_trending_data
34
35
36 class Layout:
37     """
38     """
39
40     STYLE_DISABLED = {"display": "none"}
41     STYLE_ENABLED = {"display": "inherit"}
42
43     CL_ALL_DISABLED = [{
44         "label": "All",
45         "value": "all",
46         "disabled": True
47     }]
48     CL_ALL_ENABLED = [{
49         "label": "All",
50         "value": "all",
51         "disabled": False
52     }]
53
54     PLACEHOLDER = html.Nobr("")
55
56     DRIVERS = ("avf", "af-xdp", "rdma", "dpdk")
57
58     LABELS = {
59         "dpdk": "DPDK",
60         "container_memif": "LXC/DRC Container Memif",
61         "crypto": "IPSec IPv4 Routing",
62         "ip4": "IPv4 Routing",
63         "ip6": "IPv6 Routing",
64         "ip4_tunnels": "IPv4 Tunnels",
65         "l2": "L2 Ethernet Switching",
66         "srv6": "SRv6 Routing",
67         "vm_vhost": "VMs vhost-user",
68         "nfv_density-dcr_memif-chain_ipsec": "CNF Service Chains Routing IPSec",
69         "nfv_density-vm_vhost-chain_dot1qip4vxlan":"VNF Service Chains Tunnels",
70         "nfv_density-vm_vhost-chain": "VNF Service Chains Routing",
71         "nfv_density-dcr_memif-pipeline": "CNF Service Pipelines Routing",
72         "nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
73     }
74
75     def __init__(self, app: Flask, html_layout_file: str, spec_file: str,
76         graph_layout_file: str, data_spec_file: str,
77         time_period: str=None) -> None:
78         """
79         """
80
81         # Inputs
82         self._app = app
83         self._html_layout_file = html_layout_file
84         self._spec_file = spec_file
85         self._graph_layout_file = graph_layout_file
86         self._data_spec_file = data_spec_file
87         self._time_period = time_period
88
89         # Read the data:
90         data_mrr = Data(
91             data_spec_file=self._data_spec_file,
92             debug=True
93         ).read_trending_mrr(days=self._time_period)
94
95         data_ndrpdr = Data(
96             data_spec_file=self._data_spec_file,
97             debug=True
98         ).read_trending_ndrpdr(days=self._time_period)
99
100         self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
101
102         data_time_period = \
103             (datetime.utcnow() - self._data["start_time"].min()).days
104         if self._time_period > data_time_period:
105             self._time_period = data_time_period
106
107
108         # Get structure of tests:
109         tbs = dict()
110         for _, row in self._data[["job", "test_id"]].drop_duplicates().\
111                 iterrows():
112             lst_job = row["job"].split("-")
113             dut = lst_job[1]
114             ttype = lst_job[3]
115             tbed = "-".join(lst_job[-2:])
116             lst_test = row["test_id"].split(".")
117             if dut == "dpdk":
118                 area = "dpdk"
119             else:
120                 area = "-".join(lst_test[3:-2])
121             suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
122                 replace("2n-", "")
123             test = lst_test[-1]
124             nic = suite.split("-")[0]
125             for drv in self.DRIVERS:
126                 if drv in test:
127                     if drv == "af-xdp":
128                         driver = "af_xdp"
129                     else:
130                         driver = drv
131                     test = test.replace(f"{drv}-", "")
132                     break
133             else:
134                 driver = "dpdk"
135             infra = "-".join((tbed, nic, driver))
136             lst_test = test.split("-")
137             framesize = lst_test[0]
138             core = lst_test[1] if lst_test[1] else "1C"
139             test = "-".join(lst_test[2: -1])
140
141             if tbs.get(dut, None) is None:
142                 tbs[dut] = dict()
143             if tbs[dut].get(infra, None) is None:
144                 tbs[dut][infra] = dict()
145             if tbs[dut][infra].get(area, None) is None:
146                 tbs[dut][infra][area] = dict()
147             if tbs[dut][infra][area].get(test, None) is None:
148                 tbs[dut][infra][area][test] = dict()
149                 tbs[dut][infra][area][test]["core"] = list()
150                 tbs[dut][infra][area][test]["frame-size"] = list()
151                 tbs[dut][infra][area][test]["test-type"] = list()
152             if core.upper() not in tbs[dut][infra][area][test]["core"]:
153                 tbs[dut][infra][area][test]["core"].append(core.upper())
154             if framesize.upper() not in \
155                     tbs[dut][infra][area][test]["frame-size"]:
156                 tbs[dut][infra][area][test]["frame-size"].append(
157                     framesize.upper())
158             if ttype == "mrr":
159                 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
160                     tbs[dut][infra][area][test]["test-type"].append("MRR")
161             elif ttype == "ndrpdr":
162                 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
163                     tbs[dut][infra][area][test]["test-type"].extend(
164                         ("NDR", "PDR"))
165         self._spec_tbs = tbs
166
167         # Read from files:
168         self._html_layout = ""
169         self._graph_layout = None
170
171         try:
172             with open(self._html_layout_file, "r") as file_read:
173                 self._html_layout = file_read.read()
174         except IOError as err:
175             raise RuntimeError(
176                 f"Not possible to open the file {self._html_layout_file}\n{err}"
177             )
178
179         try:
180             with open(self._graph_layout_file, "r") as file_read:
181                 self._graph_layout = load(file_read, Loader=FullLoader)
182         except IOError as err:
183             raise RuntimeError(
184                 f"Not possible to open the file {self._graph_layout_file}\n"
185                 f"{err}"
186             )
187         except YAMLError as err:
188             raise RuntimeError(
189                 f"An error occurred while parsing the specification file "
190                 f"{self._graph_layout_file}\n"
191                 f"{err}"
192             )
193
194         # Callbacks:
195         if self._app is not None and hasattr(self, 'callbacks'):
196             self.callbacks(self._app)
197
198     @property
199     def html_layout(self):
200         return self._html_layout
201
202     @property
203     def spec_tbs(self):
204         return self._spec_tbs
205
206     @property
207     def data(self):
208         return self._data
209
210     @property
211     def layout(self):
212         return self._graph_layout
213
214     @property
215     def time_period(self):
216         return self._time_period
217
218     def label(self, key: str) -> str:
219         return self.LABELS.get(key, key)
220
221     def add_content(self):
222         """
223         """
224         if self.html_layout and self.spec_tbs:
225             return html.Div(
226                 id="div-main",
227                 children=[
228                     dbc.Row(
229                         id="row-navbar",
230                         class_name="g-0",
231                         children=[
232                             self._add_navbar(),
233                         ]
234                     ),
235                     dcc.Loading(
236                         dbc.Offcanvas(
237                             class_name="w-50",
238                             id="offcanvas-metadata",
239                             title="Throughput And Latency",
240                             placement="end",
241                             is_open=False,
242                             children=[
243                                 dbc.Row(id="metadata-tput-lat"),
244                                 dbc.Row(id="metadata-hdrh-graph"),
245                             ]
246                         )
247                     ),
248                     dbc.Row(
249                         id="row-main",
250                         class_name="g-0",
251                         children=[
252                             dcc.Store(
253                                 id="selected-tests"
254                             ),
255                             dcc.Store(
256                                 id="control-panel"
257                             ),
258                             self._add_ctrl_col(),
259                             self._add_plotting_col(),
260                         ]
261                     )
262                 ]
263             )
264         else:
265             return html.Div(
266                 id="div-main-error",
267                 children=[
268                     dbc.Alert(
269                         [
270                             "An Error Occured",
271                         ],
272                         color="danger",
273                     ),
274                 ]
275             )
276
277     def _add_navbar(self):
278         """Add nav element with navigation panel. It is placed on the top.
279         """
280         return dbc.NavbarSimple(
281             id="navbarsimple-main",
282             children=[
283                 dbc.NavItem(
284                     dbc.NavLink(
285                         "Continuous Performance Trending",
286                         disabled=True,
287                         external_link=True,
288                         href="#"
289                     )
290                 )
291             ],
292             brand="Dashboard",
293             brand_href="/",
294             brand_external_link=True,
295             class_name="p-2",
296             fluid=True,
297         )
298
299     def _add_ctrl_col(self) -> dbc.Col:
300         """Add column with controls. It is placed on the left side.
301         """
302         return dbc.Col(
303             id="col-controls",
304             children=[
305                 self._add_ctrl_panel(),
306             ],
307         )
308
309     def _add_plotting_col(self) -> dbc.Col:
310         """Add column with plots and tables. It is placed on the right side.
311         """
312         return dbc.Col(
313             id="col-plotting-area",
314             children=[
315                 dcc.Loading(
316                     children=[
317                         dbc.Row(  # Throughput
318                             id="row-graph-tput",
319                             class_name="g-0 p-2",
320                             children=[
321                                 self.PLACEHOLDER
322                             ]
323                         ),
324                         dbc.Row(  # Latency
325                             id="row-graph-lat",
326                             class_name="g-0 p-2",
327                             children=[
328                                 self.PLACEHOLDER
329                             ]
330                         ),
331                         dbc.Row(  # Download
332                             id="row-btn-download",
333                             class_name="g-0 p-2",
334                             children=[
335                                 self.PLACEHOLDER
336                             ]
337                         )
338                     ]
339                 )
340             ],
341             width=9,
342         )
343
344     def _add_ctrl_panel(self) -> dbc.Row:
345         """
346         """
347         return dbc.Row(
348             id="row-ctrl-panel",
349             class_name="g-0 p-2",
350             children=[
351                 dbc.Row(
352                     class_name="g-0",
353                     children=[
354                         dbc.InputGroup(
355                             [
356                                 dbc.InputGroupText("DUT"),
357                                 dbc.Select(
358                                     id="dd-ctrl-dut",
359                                     placeholder=(
360                                         "Select a Device under Test..."
361                                     ),
362                                     options=sorted(
363                                         [
364                                             {"label": k, "value": k} \
365                                                 for k in self.spec_tbs.keys()
366                                         ],
367                                         key=lambda d: d["label"]
368                                     )
369                                 )
370                             ],
371                             class_name="mb-3",
372                             size="sm",
373                         ),
374                     ]
375                 ),
376                 dbc.Row(
377                     class_name="g-0",
378                     children=[
379                         dbc.InputGroup(
380                             [
381                                 dbc.InputGroupText("Infra"),
382                                 dbc.Select(
383                                     id="dd-ctrl-phy",
384                                     placeholder=(
385                                         "Select a Physical Test Bed "
386                                         "Topology..."
387                                     )
388                                 )
389                             ],
390                             class_name="mb-3",
391                             size="sm",
392                         ),
393                     ]
394                 ),
395                 dbc.Row(
396                     class_name="g-0",
397                     children=[
398                         dbc.InputGroup(
399                             [
400                                 dbc.InputGroupText("Area"),
401                                 dbc.Select(
402                                     id="dd-ctrl-area",
403                                     placeholder="Select an Area...",
404                                     disabled=True,
405                                 ),
406                             ],
407                             class_name="mb-3",
408                             size="sm",
409                         ),
410                     ]
411                 ),
412                 dbc.Row(
413                     class_name="g-0",
414                     children=[
415                         dbc.InputGroup(
416                             [
417                                 dbc.InputGroupText("Test"),
418                                 dbc.Select(
419                                     id="dd-ctrl-test",
420                                     placeholder="Select a Test...",
421                                     disabled=True,
422                                 ),
423                             ],
424                             class_name="mb-3",
425                             size="sm",
426                         ),
427                     ]
428                 ),
429                 dbc.Row(
430                     id="row-ctrl-core",
431                     class_name="gy-1",
432                     children=[
433                         dbc.Label(
434                             "Number of Cores",
435                             class_name="p-0"
436                         ),
437                         dbc.Col(
438                             children=[
439                                 dbc.Checklist(
440                                     id="cl-ctrl-core-all",
441                                     options=self.CL_ALL_DISABLED,
442                                     inline=False,
443                                     switch=False
444                                 )
445                             ],
446                             width=3
447                         ),
448                         dbc.Col(
449                             children=[
450                                 dbc.Checklist(
451                                     id="cl-ctrl-core",
452                                     inline=True,
453                                     switch=False
454                                 )
455                             ]
456                         )
457                     ]
458                 ),
459                 dbc.Row(
460                     id="row-ctrl-framesize",
461                     class_name="gy-1",
462                     children=[
463                         dbc.Label(
464                             "Frame Size",
465                             class_name="p-0"
466                         ),
467                         dbc.Col(
468                             children=[
469                                 dbc.Checklist(
470                                     id="cl-ctrl-framesize-all",
471                                     options=self.CL_ALL_DISABLED,
472                                     inline=True,
473                                     switch=False
474                                 ),
475                             ],
476                             width=3
477                         ),
478                         dbc.Col(
479                             children=[
480                                 dbc.Checklist(
481                                     id="cl-ctrl-framesize",
482                                     inline=True,
483                                     switch=False
484                                 )
485                             ]
486                         )
487                     ]
488                 ),
489                 dbc.Row(
490                     id="row-ctrl-testtype",
491                     class_name="gy-1",
492                     children=[
493                         dbc.Label(
494                             "Test Type",
495                             class_name="p-0"
496                         ),
497                         dbc.Col(
498                             children=[
499                                 dbc.Checklist(
500                                     id="cl-ctrl-testtype-all",
501                                     options=self.CL_ALL_DISABLED,
502                                     inline=True,
503                                     switch=False
504                                 ),
505                             ],
506                             width=3
507                         ),
508                         dbc.Col(
509                             children=[
510                                 dbc.Checklist(
511                                     id="cl-ctrl-testtype",
512                                     inline=True,
513                                     switch=False
514                                 )
515                             ]
516                         )
517                     ]
518                 ),
519                 dbc.Row(
520                     class_name="gy-1 p-0",
521                     children=[
522                         dbc.ButtonGroup(
523                             [
524                                 dbc.Button(
525                                     id="btn-ctrl-add",
526                                     children="Add Selected",
527                                     class_name="me-1",
528                                     color="info"
529                                 )
530                             ],
531                             size="md",
532                         )
533                     ]
534                 ),
535                 dbc.Row(
536                     class_name="gy-1",
537                     children=[
538                         dcc.DatePickerRange(
539                             id="dpr-period",
540                             className="d-flex justify-content-center",
541                             min_date_allowed=\
542                                 datetime.utcnow() - timedelta(
543                                     days=self.time_period),
544                             max_date_allowed=datetime.utcnow(),
545                             initial_visible_month=datetime.utcnow(),
546                             start_date=\
547                                 datetime.utcnow() - timedelta(
548                                     days=self.time_period),
549                             end_date=datetime.utcnow(),
550                             display_format="D MMMM YY"
551                         )
552                     ]
553                 ),
554                 dbc.Row(
555                     id="row-card-sel-tests",
556                     class_name="gy-1",
557                     style=self.STYLE_DISABLED,
558                     children=[
559                         dbc.Label(
560                             "Selected tests",
561                             class_name="p-0"
562                         ),
563                         dbc.Checklist(
564                             class_name="overflow-auto",
565                             id="cl-selected",
566                             options=[],
567                             inline=False,
568                             style={"max-height": "12em"},
569                         )
570                     ],
571                 ),
572                 dbc.Row(
573                     id="row-btns-sel-tests",
574                     style=self.STYLE_DISABLED,
575                     children=[
576                         dbc.ButtonGroup(
577                             class_name="gy-2",
578                             children=[
579                                 dbc.Button(
580                                     id="btn-sel-remove",
581                                     children="Remove Selected",
582                                     class_name="w-100 me-1",
583                                     color="info",
584                                     disabled=False
585                                 ),
586                                 dbc.Button(
587                                     id="btn-sel-remove-all",
588                                     children="Remove All",
589                                     class_name="w-100 me-1",
590                                     color="info",
591                                     disabled=False
592                                 ),
593                             ],
594                             size="md",
595                         )
596                     ]
597                 ),
598             ]
599         )
600
601     class ControlPanel:
602         def __init__(self, panel: dict) -> None:
603
604             CL_ALL_DISABLED = [{
605                 "label": "All",
606                 "value": "all",
607                 "disabled": True
608             }]
609
610             # Defines also the order of keys
611             self._defaults = {
612                 "dd-ctrl-dut-value": str(),
613                 "dd-ctrl-phy-options": list(),
614                 "dd-ctrl-phy-disabled": True,
615                 "dd-ctrl-phy-value": str(),
616                 "dd-ctrl-area-options": list(),
617                 "dd-ctrl-area-disabled": True,
618                 "dd-ctrl-area-value": str(),
619                 "dd-ctrl-test-options": list(),
620                 "dd-ctrl-test-disabled": True,
621                 "dd-ctrl-test-value": str(),
622                 "cl-ctrl-core-options": list(),
623                 "cl-ctrl-core-value": list(),
624                 "cl-ctrl-core-all-value": list(),
625                 "cl-ctrl-core-all-options": CL_ALL_DISABLED,
626                 "cl-ctrl-framesize-options": list(),
627                 "cl-ctrl-framesize-value": list(),
628                 "cl-ctrl-framesize-all-value": list(),
629                 "cl-ctrl-framesize-all-options": CL_ALL_DISABLED,
630                 "cl-ctrl-testtype-options": list(),
631                 "cl-ctrl-testtype-value": list(),
632                 "cl-ctrl-testtype-all-value": list(),
633                 "cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
634                 "btn-ctrl-add-disabled": True,
635                 "cl-selected-options": list(),
636             }
637
638             self._panel = deepcopy(self._defaults)
639             if panel:
640                 for key in self._defaults:
641                     self._panel[key] = panel[key]
642
643         @property
644         def defaults(self) -> dict:
645             return self._defaults
646
647         @property
648         def panel(self) -> dict:
649             return self._panel
650
651         def set(self, kwargs: dict) -> None:
652             for key, val in kwargs.items():
653                 if key in self._panel:
654                     self._panel[key] = val
655                 else:
656                     raise KeyError(f"The key {key} is not defined.")
657
658         def get(self, key: str) -> any:
659             return self._panel[key]
660
661         def values(self) -> tuple:
662             return tuple(self._panel.values())
663
664     @staticmethod
665     def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
666         """
667         """
668         options = {v["value"] for v in opt}
669         if id =="all":
670             sel = list(options) if all else list()
671         else:
672             all = ["all", ] if set(sel) == options else list()
673         return sel, all
674
675     @staticmethod
676     def _list_tests(selection: dict) -> list:
677         """Display selected tests with checkboxes
678         """
679         if selection:
680             return [{"label": v["id"], "value": v["id"]} for v in selection]
681         else:
682             return list()
683
684     def callbacks(self, app):
685
686         def _generate_plotting_arrea(args: tuple) -> tuple:
687             """
688             """
689
690             (fig_tput, fig_lat) = args
691
692             row_fig_tput = self.PLACEHOLDER
693             row_fig_lat = self.PLACEHOLDER
694             row_btn_dwnld = self.PLACEHOLDER
695
696             if fig_tput:
697                 row_fig_tput = [
698                     dcc.Graph(
699                         id={"type": "graph", "index": "tput"},
700                         figure=fig_tput
701                     )
702                 ]
703                 row_btn_dwnld = [
704                     dcc.Loading(children=[
705                         dbc.Button(
706                             id="btn-download-data",
707                             children=["Download Data"],
708                             class_name="me-1",
709                             color="info"
710                         ),
711                         dcc.Download(id="download-data")
712                     ]),
713                 ]
714             if fig_lat:
715                 row_fig_lat = [
716                     dcc.Graph(
717                         id={"type": "graph", "index": "lat"},
718                         figure=fig_lat
719                     )
720                 ]
721
722             return row_fig_tput, row_fig_lat, row_btn_dwnld
723
724         @app.callback(
725             Output("control-panel", "data"),  # Store
726             Output("selected-tests", "data"),  # Store
727             Output("row-graph-tput", "children"),
728             Output("row-graph-lat", "children"),
729             Output("row-btn-download", "children"),
730             Output("row-card-sel-tests", "style"),
731             Output("row-btns-sel-tests", "style"),
732             Output("dd-ctrl-dut", "value"),
733             Output("dd-ctrl-phy", "options"),
734             Output("dd-ctrl-phy", "disabled"),
735             Output("dd-ctrl-phy", "value"),
736             Output("dd-ctrl-area", "options"),
737             Output("dd-ctrl-area", "disabled"),
738             Output("dd-ctrl-area", "value"),
739             Output("dd-ctrl-test", "options"),
740             Output("dd-ctrl-test", "disabled"),
741             Output("dd-ctrl-test", "value"),
742             Output("cl-ctrl-core", "options"),
743             Output("cl-ctrl-core", "value"),
744             Output("cl-ctrl-core-all", "value"),
745             Output("cl-ctrl-core-all", "options"),
746             Output("cl-ctrl-framesize", "options"),
747             Output("cl-ctrl-framesize", "value"),
748             Output("cl-ctrl-framesize-all", "value"),
749             Output("cl-ctrl-framesize-all", "options"),
750             Output("cl-ctrl-testtype", "options"),
751             Output("cl-ctrl-testtype", "value"),
752             Output("cl-ctrl-testtype-all", "value"),
753             Output("cl-ctrl-testtype-all", "options"),
754             Output("btn-ctrl-add", "disabled"),
755             Output("cl-selected", "options"),  # User selection
756             State("control-panel", "data"),  # Store
757             State("selected-tests", "data"),  # Store
758             State("cl-selected", "value"),  # User selection
759             Input("dd-ctrl-dut", "value"),
760             Input("dd-ctrl-phy", "value"),
761             Input("dd-ctrl-area", "value"),
762             Input("dd-ctrl-test", "value"),
763             Input("cl-ctrl-core", "value"),
764             Input("cl-ctrl-core-all", "value"),
765             Input("cl-ctrl-framesize", "value"),
766             Input("cl-ctrl-framesize-all", "value"),
767             Input("cl-ctrl-testtype", "value"),
768             Input("cl-ctrl-testtype-all", "value"),
769             Input("btn-ctrl-add", "n_clicks"),
770             Input("dpr-period", "start_date"),
771             Input("dpr-period", "end_date"),
772             Input("btn-sel-remove", "n_clicks"),
773             Input("btn-sel-remove-all", "n_clicks"),
774         )
775         def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
776             dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
777             cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
778             cl_testtype: list, cl_testtype_all: list, btn_add: int,
779             d_start: str, d_end: str, btn_remove: int,
780             btn_remove_all: int) -> tuple:
781             """
782             """
783
784             d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
785                 int(d_start[8:10]))
786             d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
787
788             row_fig_tput = no_update
789             row_fig_lat = no_update
790             row_btn_dwnld = no_update
791             row_card_sel_tests = no_update
792             row_btns_sel_tests = no_update
793
794             ctrl_panel = self.ControlPanel(cp_data)
795
796             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
797
798             if trigger_id == "dd-ctrl-dut":
799                 try:
800                     options = sorted(
801                         [
802                             {"label": v, "value": v}
803                                 for v in self.spec_tbs[dd_dut].keys()
804                         ],
805                         key=lambda d: d["label"]
806                     )
807                     disabled = False
808                 except KeyError:
809                     options = list()
810                     disabled = True
811                 ctrl_panel.set({
812                     "dd-ctrl-dut-value": dd_dut,
813                     "dd-ctrl-phy-value": str(),
814                     "dd-ctrl-phy-options": options,
815                     "dd-ctrl-phy-disabled": disabled,
816                     "dd-ctrl-area-value": str(),
817                     "dd-ctrl-area-options": list(),
818                     "dd-ctrl-area-disabled": True,
819                     "dd-ctrl-test-options": list(),
820                     "dd-ctrl-test-disabled": True,
821                     "cl-ctrl-core-options": list(),
822                     "cl-ctrl-core-value": list(),
823                     "cl-ctrl-core-all-value": list(),
824                     "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
825                     "cl-ctrl-framesize-options": list(),
826                     "cl-ctrl-framesize-value": list(),
827                     "cl-ctrl-framesize-all-value": list(),
828                     "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
829                     "cl-ctrl-testtype-options": list(),
830                     "cl-ctrl-testtype-value": list(),
831                     "cl-ctrl-testtype-all-value": list(),
832                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
833                 })
834             if trigger_id == "dd-ctrl-phy":
835                 try:
836                     dut = ctrl_panel.get("dd-ctrl-dut-value")
837                     options = sorted(
838                         [
839                             {"label": self.label(v), "value": v}
840                                 for v in self.spec_tbs[dut][dd_phy].keys()
841                         ],
842                         key=lambda d: d["label"]
843                     )
844                     disabled = False
845                 except KeyError:
846                     options = list()
847                     disabled = True
848                 ctrl_panel.set({
849                     "dd-ctrl-phy-value": dd_phy,
850                     "dd-ctrl-area-value": str(),
851                     "dd-ctrl-area-options": options,
852                     "dd-ctrl-area-disabled": disabled,
853                     "dd-ctrl-test-options": list(),
854                     "dd-ctrl-test-disabled": True,
855                     "cl-ctrl-core-options": list(),
856                     "cl-ctrl-core-value": list(),
857                     "cl-ctrl-core-all-value": list(),
858                     "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
859                     "cl-ctrl-framesize-options": list(),
860                     "cl-ctrl-framesize-value": list(),
861                     "cl-ctrl-framesize-all-value": list(),
862                     "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
863                     "cl-ctrl-testtype-options": list(),
864                     "cl-ctrl-testtype-value": list(),
865                     "cl-ctrl-testtype-all-value": list(),
866                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
867                 })
868             elif trigger_id == "dd-ctrl-area":
869                 try:
870                     dut = ctrl_panel.get("dd-ctrl-dut-value")
871                     phy = ctrl_panel.get("dd-ctrl-phy-value")
872                     options = sorted(
873                         [
874                             {"label": v, "value": v}
875                                 for v in self.spec_tbs[dut][phy][dd_area].keys()
876                         ],
877                         key=lambda d: d["label"]
878                     )
879                     disabled = False
880                 except KeyError:
881                     options = list()
882                     disabled = True
883                 ctrl_panel.set({
884                     "dd-ctrl-area-value": dd_area,
885                     "dd-ctrl-test-value": str(),
886                     "dd-ctrl-test-options": options,
887                     "dd-ctrl-test-disabled": disabled,
888                     "cl-ctrl-core-options": list(),
889                     "cl-ctrl-core-value": list(),
890                     "cl-ctrl-core-all-value": list(),
891                     "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
892                     "cl-ctrl-framesize-options": list(),
893                     "cl-ctrl-framesize-value": list(),
894                     "cl-ctrl-framesize-all-value": list(),
895                     "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
896                     "cl-ctrl-testtype-options": list(),
897                     "cl-ctrl-testtype-value": list(),
898                     "cl-ctrl-testtype-all-value": list(),
899                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
900                 })
901             elif trigger_id == "dd-ctrl-test":
902                 core_opts = list()
903                 framesize_opts = list()
904                 testtype_opts = list()
905                 dut = ctrl_panel.get("dd-ctrl-dut-value")
906                 phy = ctrl_panel.get("dd-ctrl-phy-value")
907                 area = ctrl_panel.get("dd-ctrl-area-value")
908                 cores = self.spec_tbs[dut][phy][area][dd_test]["core"]
909                 fsizes = self.spec_tbs[dut][phy][area][dd_test]["frame-size"]
910                 ttypes = self.spec_tbs[dut][phy][area][dd_test]["test-type"]
911                 if dut and phy and area and dd_test:
912                     core_opts = [
913                         {"label": v, "value": v} for v in sorted(cores)
914                     ]
915                     framesize_opts = [
916                         {"label": v, "value": v} for v in sorted(fsizes)
917                     ]
918                     testtype_opts = [
919                         {"label": v, "value": v}for v in sorted(ttypes)
920                     ]
921                     ctrl_panel.set({
922                         "dd-ctrl-test-value": dd_test,
923                         "cl-ctrl-core-options": core_opts,
924                         "cl-ctrl-core-value": list(),
925                         "cl-ctrl-core-all-value": list(),
926                         "cl-ctrl-core-all-options": self.CL_ALL_ENABLED,
927                         "cl-ctrl-framesize-options": framesize_opts,
928                         "cl-ctrl-framesize-value": list(),
929                         "cl-ctrl-framesize-all-value": list(),
930                         "cl-ctrl-framesize-all-options": self.CL_ALL_ENABLED,
931                         "cl-ctrl-testtype-options": testtype_opts,
932                         "cl-ctrl-testtype-value": list(),
933                         "cl-ctrl-testtype-all-value": list(),
934                         "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
935                     })
936             elif trigger_id == "cl-ctrl-core":
937                 val_sel, val_all = self._sync_checklists(
938                     opt=ctrl_panel.get("cl-ctrl-core-options"),
939                     sel=cl_core,
940                     all=list(),
941                     id=""
942                 )
943                 ctrl_panel.set({
944                     "cl-ctrl-core-value": val_sel,
945                     "cl-ctrl-core-all-value": val_all,
946                 })
947             elif trigger_id == "cl-ctrl-core-all":
948                 val_sel, val_all = self._sync_checklists(
949                     opt = ctrl_panel.get("cl-ctrl-core-options"),
950                     sel=list(),
951                     all=cl_core_all,
952                     id="all"
953                 )
954                 ctrl_panel.set({
955                     "cl-ctrl-core-value": val_sel,
956                     "cl-ctrl-core-all-value": val_all,
957                 })
958             elif trigger_id == "cl-ctrl-framesize":
959                 val_sel, val_all = self._sync_checklists(
960                     opt = ctrl_panel.get("cl-ctrl-framesize-options"),
961                     sel=cl_framesize,
962                     all=list(),
963                     id=""
964                 )
965                 ctrl_panel.set({
966                     "cl-ctrl-framesize-value": val_sel,
967                     "cl-ctrl-framesize-all-value": val_all,
968                 })
969             elif trigger_id == "cl-ctrl-framesize-all":
970                 val_sel, val_all = self._sync_checklists(
971                     opt = ctrl_panel.get("cl-ctrl-framesize-options"),
972                     sel=list(),
973                     all=cl_framesize_all,
974                     id="all"
975                 )
976                 ctrl_panel.set({
977                     "cl-ctrl-framesize-value": val_sel,
978                     "cl-ctrl-framesize-all-value": val_all,
979                 })
980             elif trigger_id == "cl-ctrl-testtype":
981                 val_sel, val_all = self._sync_checklists(
982                     opt = ctrl_panel.get("cl-ctrl-testtype-options"),
983                     sel=cl_testtype,
984                     all=list(),
985                     id=""
986                 )
987                 ctrl_panel.set({
988                     "cl-ctrl-testtype-value": val_sel,
989                     "cl-ctrl-testtype-all-value": val_all,
990                 })
991             elif trigger_id == "cl-ctrl-testtype-all":
992                 val_sel, val_all = self._sync_checklists(
993                     opt = ctrl_panel.get("cl-ctrl-testtype-options"),
994                     sel=list(),
995                     all=cl_testtype_all,
996                     id="all"
997                 )
998                 ctrl_panel.set({
999                     "cl-ctrl-testtype-value": val_sel,
1000                     "cl-ctrl-testtype-all-value": val_all,
1001                 })
1002             elif trigger_id == "btn-ctrl-add":
1003                 _ = btn_add
1004                 dut = ctrl_panel.get("dd-ctrl-dut-value")
1005                 phy = ctrl_panel.get("dd-ctrl-phy-value")
1006                 area = ctrl_panel.get("dd-ctrl-area-value")
1007                 test = ctrl_panel.get("dd-ctrl-test-value")
1008                 cores = ctrl_panel.get("cl-ctrl-core-value")
1009                 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1010                 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1011                 # Add selected test to the list of tests in store:
1012                 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1013                     if store_sel is None:
1014                         store_sel = list()
1015                     for core in cores:
1016                         for framesize in framesizes:
1017                             for ttype in testtypes:
1018                                 if dut == "trex":
1019                                     core = str()
1020                                 tid = "-".join((
1021                                     dut, phy.replace('af_xdp', 'af-xdp'), area,
1022                                     framesize.lower(), core.lower(), test,
1023                                     ttype.lower()
1024                                 ))
1025                                 if tid not in [itm["id"] for itm in store_sel]:
1026                                     store_sel.append({
1027                                         "id": tid,
1028                                         "dut": dut,
1029                                         "phy": phy,
1030                                         "area": area,
1031                                         "test": test,
1032                                         "framesize": framesize.lower(),
1033                                         "core": core.lower(),
1034                                         "testtype": ttype.lower()
1035                                     })
1036                     store_sel = sorted(store_sel, key=lambda d: d["id"])
1037                     row_card_sel_tests = self.STYLE_ENABLED
1038                     row_btns_sel_tests = self.STYLE_ENABLED
1039                     ctrl_panel.set(ctrl_panel.defaults)
1040                     ctrl_panel.set({
1041                         "cl-selected-options": self._list_tests(store_sel)
1042                     })
1043                     row_fig_tput, row_fig_lat, row_btn_dwnld = \
1044                         _generate_plotting_arrea(
1045                             graph_trending(
1046                                 self.data, store_sel, self.layout, d_start,
1047                                 d_end
1048                             )
1049                         )
1050             elif trigger_id == "dpr-period":
1051                 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1052                     _generate_plotting_arrea(
1053                         graph_trending(
1054                             self.data, store_sel, self.layout, d_start, d_end
1055                         )
1056                     )
1057             elif trigger_id == "btn-sel-remove-all":
1058                 _ = btn_remove_all
1059                 row_fig_tput = self.PLACEHOLDER
1060                 row_fig_lat = self.PLACEHOLDER
1061                 row_btn_dwnld = self.PLACEHOLDER
1062                 row_card_sel_tests = self.STYLE_DISABLED
1063                 row_btns_sel_tests = self.STYLE_DISABLED
1064                 store_sel = list()
1065                 ctrl_panel.set({
1066                         "cl-selected-options": list()
1067                 })
1068             elif trigger_id == "btn-sel-remove":
1069                 _ = btn_remove
1070                 if list_sel:
1071                     new_store_sel = list()
1072                     for item in store_sel:
1073                         if item["id"] not in list_sel:
1074                             new_store_sel.append(item)
1075                     store_sel = new_store_sel
1076                 if store_sel:
1077                     row_fig_tput, row_fig_lat, row_btn_dwnld = \
1078                         _generate_plotting_arrea(
1079                             graph_trending(
1080                                 self.data, store_sel, self.layout, d_start,
1081                                 d_end
1082                             )
1083                         )
1084                     ctrl_panel.set({
1085                         "cl-selected-options": self._list_tests(store_sel)
1086                     })
1087                 else:
1088                     row_fig_tput = self.PLACEHOLDER
1089                     row_fig_lat = self.PLACEHOLDER
1090                     row_btn_dwnld = self.PLACEHOLDER
1091                     row_card_sel_tests = self.STYLE_DISABLED
1092                     row_btns_sel_tests = self.STYLE_DISABLED
1093                     store_sel = list()
1094                     ctrl_panel.set({
1095                             "cl-selected-options": list()
1096                     })
1097
1098             if ctrl_panel.get("cl-ctrl-core-value") and \
1099                     ctrl_panel.get("cl-ctrl-framesize-value") and \
1100                     ctrl_panel.get("cl-ctrl-testtype-value"):
1101                 disabled = False
1102             else:
1103                 disabled = True
1104             ctrl_panel.set({
1105                 "btn-ctrl-add-disabled": disabled
1106             })
1107
1108             ret_val = [
1109                 ctrl_panel.panel, store_sel,
1110                 row_fig_tput, row_fig_lat, row_btn_dwnld,
1111                 row_card_sel_tests, row_btns_sel_tests
1112             ]
1113             ret_val.extend(ctrl_panel.values())
1114             return ret_val
1115
1116         @app.callback(
1117             Output("metadata-tput-lat", "children"),
1118             Output("metadata-hdrh-graph", "children"),
1119             Output("offcanvas-metadata", "is_open"),
1120             Input({"type": "graph", "index": ALL}, "clickData"),
1121             prevent_initial_call=True
1122         )
1123         def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1124             """
1125             """
1126             try:
1127                 trigger_id = loads(
1128                     callback_context.triggered[0]["prop_id"].split(".")[0]
1129                 )["index"]
1130                 idx = 0 if trigger_id == "tput" else 1
1131                 graph_data = graph_data[idx]["points"][0]
1132             except (JSONDecodeError, IndexError, KeyError, ValueError,
1133                     TypeError):
1134                 raise PreventUpdate
1135
1136             metadata = no_update
1137             graph = list()
1138
1139             children = [
1140                 dbc.ListGroupItem(
1141                     [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1142                 ) for x in graph_data.get("text", "").split("<br>")
1143             ]
1144             if trigger_id == "tput":
1145                 title = "Throughput"
1146             elif trigger_id == "lat":
1147                 title = "Latency"
1148                 hdrh_data = graph_data.get("customdata", None)
1149                 if hdrh_data:
1150                     graph = [dbc.Card(
1151                         class_name="gy-2 p-0",
1152                         children=[
1153                             dbc.CardHeader(hdrh_data.pop("name")),
1154                             dbc.CardBody(children=[
1155                                 dcc.Graph(
1156                                     id="hdrh-latency-graph",
1157                                     figure=graph_hdrh_latency(
1158                                         hdrh_data, self.layout
1159                                     )
1160                                 )
1161                             ])
1162                         ])
1163                     ]
1164             metadata = [
1165                 dbc.Card(
1166                     class_name="gy-2 p-0",
1167                     children=[
1168                         dbc.CardHeader(children=[
1169                             dcc.Clipboard(
1170                                 target_id="tput-lat-metadata",
1171                                 title="Copy",
1172                                 style={"display": "inline-block"}
1173                             ),
1174                             title
1175                         ]),
1176                         dbc.CardBody(
1177                             id="tput-lat-metadata",
1178                             class_name="p-0",
1179                             children=[dbc.ListGroup(children, flush=True), ]
1180                         )
1181                     ]
1182                 )
1183             ]
1184
1185             return metadata, graph, True
1186
1187         @app.callback(
1188             Output("download-data", "data"),
1189             State("selected-tests", "data"),
1190             Input("btn-download-data", "n_clicks"),
1191             prevent_initial_call=True
1192         )
1193         def _download_data(store_sel, n_clicks):
1194             """
1195             """
1196
1197             if not n_clicks:
1198                 raise PreventUpdate
1199
1200             if not store_sel:
1201                 raise PreventUpdate
1202
1203             df = pd.DataFrame()
1204             for itm in store_sel:
1205                 sel_data = select_trending_data(self.data, itm)
1206                 if sel_data is None:
1207                     continue
1208                 df = pd.concat([df, sel_data], ignore_index=True)
1209
1210             return dcc.send_data_frame(df.to_csv, "trending_data.csv")