X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=csit.infra.dash%2Fapp%2Fcdash%2Freport%2Flayout.py;h=06b5d467dc0d17ab108e9d6df81353759724b13a;hb=55f72749a29e303c3a1049349b82baf258079e29;hp=d4720d91fcb1c989bc4a17eded6e2963897adab4;hpb=430c577e8e8a737cb46e67cbe802e038b8ffd25a;p=csit.git diff --git a/csit.infra.dash/app/cdash/report/layout.py b/csit.infra.dash/app/cdash/report/layout.py index d4720d91fc..06b5d467dc 100644 --- a/csit.infra.dash/app/cdash/report/layout.py +++ b/csit.infra.dash/app/cdash/report/layout.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 Cisco and/or its affiliates. +# Copyright (c) 2023 Cisco and/or its affiliates. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: @@ -31,10 +31,9 @@ from ..utils.constants import Constants as C from ..utils.control_panel import ControlPanel from ..utils.trigger import Trigger from ..utils.utils import show_tooltip, label, sync_checklists, gen_new_url, \ - generate_options, get_list_group_items + generate_options, get_list_group_items, graph_hdrh_latency from ..utils.url_processing import url_decode -from ..data.data import Data -from .graphs import graph_iterative, get_short_version, select_iterative_data +from .graphs import graph_iterative, select_iterative_data # Control panel partameters and their default values. @@ -76,8 +75,14 @@ class Layout: """The layout of the dash app and the callbacks. """ - def __init__(self, app: Flask, releases: list, html_layout_file: str, - graph_layout_file: str, data_spec_file: str, tooltip_file: str) -> None: + def __init__( + self, + app: Flask, + data_iterative: pd.DataFrame, + html_layout_file: str, + graph_layout_file: str, + tooltip_file: str + ) -> None: """Initialization: - save the input parameters, - read and pre-process the data, @@ -86,60 +91,41 @@ class Layout: - read tooltips from the tooltip file. :param app: Flask application running the dash application. - :param releases: Lis of releases to be displayed. :param html_layout_file: Path and name of the file specifying the HTML layout of the dash application. :param graph_layout_file: Path and name of the file with layout of plot.ly graphs. - :param data_spec_file: Path and name of the file specifying the data to - be read from parquets for this application. :param tooltip_file: Path and name of the yaml file specifying the tooltips. :type app: Flask - :type releases: list :type html_layout_file: str :type graph_layout_file: str - :type data_spec_file: str :type tooltip_file: str """ # Inputs self._app = app - self.releases = releases self._html_layout_file = html_layout_file self._graph_layout_file = graph_layout_file - self._data_spec_file = data_spec_file self._tooltip_file = tooltip_file - - # Read the data: - self._data = pd.DataFrame() - for rls in releases: - data_mrr = Data(self._data_spec_file, True).\ - read_iterative_mrr(release=rls.replace("csit", "rls")) - data_mrr["release"] = rls - data_ndrpdr = Data(self._data_spec_file, True).\ - read_iterative_ndrpdr(release=rls.replace("csit", "rls")) - data_ndrpdr["release"] = rls - self._data = pd.concat( - [self._data, data_mrr, data_ndrpdr], - ignore_index=True - ) + self._data = data_iterative # Get structure of tests: tbs = dict() - cols = ["job", "test_id", "test_type", "dut_version", "release"] + cols = [ + "job", "test_id", "test_type", "dut_version", "tg_type", "release" + ] for _, row in self._data[cols].drop_duplicates().iterrows(): rls = row["release"] - ttype = row["test_type"] lst_job = row["job"].split("-") dut = lst_job[1] - d_ver = get_short_version(row["dut_version"], dut) + d_ver = row["dut_version"] tbed = "-".join(lst_job[-2:]) lst_test_id = row["test_id"].split(".") if dut == "dpdk": area = "dpdk" else: - area = "-".join(lst_test_id[3:-2]) + area = ".".join(lst_test_id[3:-2]) suite = lst_test_id[-2].replace("2n1l-", "").replace("1n1l-", "").\ replace("2n-", "") test = lst_test_id[-1] @@ -182,18 +168,31 @@ class Layout: tbs[rls][dut][d_ver][infra][area][test]["frame-size"].append( framesize.upper() ) - if ttype == "mrr": + if row["test_type"] == "mrr": if "MRR" not in \ tbs[rls][dut][d_ver][infra][area][test]["test-type"]: tbs[rls][dut][d_ver][infra][area][test]["test-type"].append( "MRR" ) - elif ttype == "ndrpdr": + elif row["test_type"] == "ndrpdr": if "NDR" not in \ tbs[rls][dut][d_ver][infra][area][test]["test-type"]: tbs[rls][dut][d_ver][infra][area][test]["test-type"].extend( ("NDR", "PDR", ) ) + elif row["test_type"] == "hoststack" and \ + row["tg_type"] in ("iperf", "vpp"): + if "BPS" not in \ + tbs[rls][dut][d_ver][infra][area][test]["test-type"]: + tbs[rls][dut][d_ver][infra][area][test]["test-type"].append( + "BPS" + ) + elif row["test_type"] == "hoststack" and row["tg_type"] == "ab": + if "CPS" not in \ + tbs[rls][dut][d_ver][infra][area][test]["test-type"]: + tbs[rls][dut][d_ver][infra][area][test]["test-type"].extend( + ("CPS", "RPS") + ) self._spec_tbs = tbs # Read from files: @@ -280,6 +279,32 @@ class Layout: self._add_ctrl_col(), self._add_plotting_col() ] + ), + dbc.Spinner( + dbc.Offcanvas( + class_name="w-50", + id="offcanvas-metadata", + title="Throughput And Latency", + placement="end", + is_open=False, + children=[ + dbc.Row(id="metadata-tput-lat"), + dbc.Row(id="metadata-hdrh-graph") + ] + ), + delay_show=C.SPINNER_DELAY + ), + dbc.Offcanvas( + class_name="w-75", + id="offcanvas-documentation", + title="Documentation", + placement="end", + is_open=False, + children=html.Iframe( + src=C.URL_DOC_REL_NOTES, + width="100%", + height="100%" + ) ) ] ) @@ -305,14 +330,26 @@ class Layout: return dbc.NavbarSimple( id="navbarsimple-main", children=[ - dbc.NavItem( - dbc.NavLink( - C.REPORT_TITLE, - disabled=True, - external_link=True, - href="#" - ) - ) + dbc.NavItem(dbc.NavLink( + C.REPORT_TITLE, + active=True, + external_link=True, + href="/report" + )), + dbc.NavItem(dbc.NavLink( + "Comparisons", + external_link=True, + href="/comparisons" + )), + dbc.NavItem(dbc.NavLink( + "Coverage Data", + external_link=True, + href="/coverage" + )), + dbc.NavItem(dbc.NavLink( + "Documentation", + id="btn-documentation", + )) ], brand=C.BRAND, brand_href="/", @@ -343,7 +380,7 @@ class Layout: return dbc.Col( id="col-plotting-area", children=[ - dcc.Loading( + dbc.Spinner( children=[ dbc.Row( id="plotting-area", @@ -503,132 +540,164 @@ class Layout: dbc.Row( class_name="g-0 p-1", children=[ - dbc.Label( - children=show_tooltip( - self._tooltips, - "help-framesize", - "Frame Size" - ) - ), - dbc.Col( - children=[ - dbc.Checklist( - id={"type": "ctrl-cl", "index": "frmsize-all"}, - options=C.CL_ALL_DISABLED, - inline=True, - switch=False, - input_class_name="border-info bg-info" + dbc.InputGroup( + [ + dbc.InputGroupText( + children=show_tooltip( + self._tooltips, + "help-framesize", + "Frame Size" + ) + ), + dbc.Col( + children=[ + dbc.Checklist( + id={ + "type": "ctrl-cl", + "index": "frmsize-all" + }, + options=C.CL_ALL_DISABLED, + inline=True, + class_name="ms-2" + ) + ], + width=2 + ), + dbc.Col( + children=[ + dbc.Checklist( + id={ + "type": "ctrl-cl", + "index": "frmsize" + }, + inline=True + ) + ] ) ], - width=3 - ), - dbc.Col( - children=[ - dbc.Checklist( - id={"type": "ctrl-cl", "index": "frmsize"}, - inline=True, - switch=False, - input_class_name="border-info bg-info" - ) - ] + style={"align-items": "center"}, + size="sm" ) ] ), dbc.Row( class_name="g-0 p-1", children=[ - dbc.Label( - children=show_tooltip( - self._tooltips, - "help-cores", - "Number of Cores" - ) - ), - dbc.Col( - children=[ - dbc.Checklist( - id={"type": "ctrl-cl", "index": "core-all"}, - options=C.CL_ALL_DISABLED, - inline=False, - switch=False, - input_class_name="border-info bg-info" + dbc.InputGroup( + [ + dbc.InputGroupText( + children=show_tooltip( + self._tooltips, + "help-cores", + "Number of Cores" + ) + ), + dbc.Col( + children=[ + dbc.Checklist( + id={ + "type": "ctrl-cl", + "index": "core-all" + }, + options=C.CL_ALL_DISABLED, + inline=True, + class_name="ms-2" + ) + ], + width=2 + ), + dbc.Col( + children=[ + dbc.Checklist( + id={ + "type": "ctrl-cl", + "index": "core" + }, + inline=True + ) + ] ) ], - width=3 - ), - dbc.Col( - children=[ - dbc.Checklist( - id={"type": "ctrl-cl", "index": "core"}, - inline=True, - switch=False, - input_class_name="border-info bg-info" - ) - ] + style={"align-items": "center"}, + size="sm" ) ] ), dbc.Row( class_name="g-0 p-1", children=[ - dbc.Label( - children=show_tooltip( - self._tooltips, - "help-ttype", - "Test Type" - ) - ), - dbc.Col( - children=[ - dbc.Checklist( - id={"type": "ctrl-cl", "index": "tsttype-all"}, - options=C.CL_ALL_DISABLED, - inline=True, - switch=False, - input_class_name="border-info bg-info" + dbc.InputGroup( + [ + dbc.InputGroupText( + children=show_tooltip( + self._tooltips, + "help-ttype", + "Test Type" + ) + ), + dbc.Col( + children=[ + dbc.Checklist( + id={ + "type": "ctrl-cl", + "index": "tsttype-all" + }, + options=C.CL_ALL_DISABLED, + inline=True, + class_name="ms-2" + ) + ], + width=2 + ), + dbc.Col( + children=[ + dbc.Checklist( + id={ + "type": "ctrl-cl", + "index": "tsttype" + }, + inline=True + ) + ] ) ], - width=3 - ), - dbc.Col( - children=[ - dbc.Checklist( - id={"type": "ctrl-cl", "index": "tsttype"}, - inline=True, - switch=False, - input_class_name="border-info bg-info" - ) - ] + style={"align-items": "center"}, + size="sm" ) ] ), dbc.Row( class_name="g-0 p-1", children=[ - dbc.Label( - children=show_tooltip( - self._tooltips, - "help-normalize", - "Normalize" - ) - ), - dbc.Col( - children=[ - dbc.Checklist( - id="normalize", - options=[{ - "value": "normalize", - "label": ( - "Normalize results to CPU " - "frequency 2GHz" - ) - }], - value=[], - inline=True, - switch=False, - input_class_name="border-info bg-info" + dbc.InputGroup( + [ + dbc.InputGroupText( + children=show_tooltip( + self._tooltips, + "help-normalize", + "Normalization" + ) ), - ] + dbc.Col( + children=[ + dbc.Checklist( + id="normalize", + options=[{ + "value": "normalize", + "label": ( + "Normalize to CPU frequency " + "2GHz" + ) + }], + value=[], + inline=True, + class_name="ms-2" + ) + ] + ) + ], + style={"align-items": "center"}, + size="sm" ) ] ), @@ -651,7 +720,7 @@ class Layout: class_name="overflow-auto p-0", id="lg-selected", children=[], - style={"max-height": "14em"}, + style={"max-height": "20em"}, flush=True ) ] @@ -743,7 +812,7 @@ class Layout: [ dbc.Button( id="plot-btn-url", - children="URL", + children="Show URL", class_name="me-1", color="info", style={ @@ -874,7 +943,7 @@ class Layout: try: store_sel = literal_eval(url_params["store_sel"][0]) normalize = literal_eval(url_params["norm"][0]) - except (KeyError, IndexError): + except (KeyError, IndexError, AttributeError): pass if store_sel: row_card_sel_tests = C.STYLE_ENABLED @@ -1158,7 +1227,7 @@ class Layout: f"cl-{param}-val": val_sel, f"cl-{param}-all-val": val_all, }) - if all((ctrl_panel.get("cl-core-val"), + if all((ctrl_panel.get("cl-core-val"), ctrl_panel.get("cl-frmsize-val"), ctrl_panel.get("cl-tsttype-val"), )): ctrl_panel.set({"btn-add-dis": False}) @@ -1219,7 +1288,9 @@ class Layout: if on_draw: if store_sel: - lg_selected = get_list_group_items(store_sel) + lg_selected = get_list_group_items( + store_sel, "sel-cl", add_index=True + ) plotting_area = self._get_plotting_area( store_sel, bool(normalize), @@ -1265,7 +1336,7 @@ class Layout: Input("plot-btn-download", "n_clicks"), prevent_initial_call=True ) - def _download_trending_data(store_sel, _): + def _download_iterative_data(store_sel, _): """Download the data :param store_sel: List of tests selected by user stored in the @@ -1287,3 +1358,122 @@ class Layout: df = pd.concat([df, sel_data], ignore_index=True) return dcc.send_data_frame(df.to_csv, C.REPORT_DOWNLOAD_FILE_NAME) + + @app.callback( + Output("metadata-tput-lat", "children"), + Output("metadata-hdrh-graph", "children"), + Output("offcanvas-metadata", "is_open"), + Input({"type": "graph", "index": ALL}, "clickData"), + prevent_initial_call=True + ) + def _show_metadata_from_graphs(graph_data: dict) -> tuple: + """Generates the data for the offcanvas displayed when a particular + point in a graph is clicked on. + + :param graph_data: The data from the clicked point in the graph. + :type graph_data: dict + :returns: The data to be displayed on the offcanvas and the + information to show the offcanvas. + :rtype: tuple(list, list, bool) + """ + + trigger = Trigger(callback_context.triggered) + + try: + idx = 0 if trigger.idx == "tput" else 1 + graph_data = graph_data[idx]["points"] + except (IndexError, KeyError, ValueError, TypeError): + raise PreventUpdate + + def _process_stats(data: list, param: str) -> list: + """Process statistical data provided by plot.ly box graph. + + :param data: Statistical data provided by plot.ly box graph. + :param param: Parameter saying if the data come from "tput" or + "lat" graph. + :type data: list + :type param: str + :returns: Listo of tuples where the first value is the + statistic's name and the secont one it's value. + :rtype: list + """ + if len(data) == 7: + stats = ("max", "upper fence", "q3", "median", "q1", + "lower fence", "min") + elif len(data) == 9: + stats = ("outlier", "max", "upper fence", "q3", "median", + "q1", "lower fence", "min", "outlier") + elif len(data) == 1: + if param == "lat": + stats = ("Average Latency at 50% PDR", ) + else: + stats = ("Throughput", ) + else: + return list() + unit = " [us]" if param == "lat" else str() + return [(f"{stat}{unit}", f"{value['y']:,.0f}") + for stat, value in zip(stats, data)] + + graph = list() + if trigger.idx == "tput": + title = "Throughput" + elif trigger.idx == "lat": + title = "Latency" + if len(graph_data) == 1: + hdrh_data = graph_data[0].get("customdata", None) + if hdrh_data: + graph = [dbc.Card( + class_name="gy-2 p-0", + children=[ + dbc.CardHeader(hdrh_data.pop("name")), + dbc.CardBody(children=[ + dcc.Graph( + id="hdrh-latency-graph", + figure=graph_hdrh_latency( + hdrh_data, self._graph_layout + ) + ) + ]) + ]) + ] + else: + raise PreventUpdate + metadata = [ + dbc.Card( + class_name="gy-2 p-0", + children=[ + dbc.CardHeader(children=[ + dcc.Clipboard( + target_id="tput-lat-metadata", + title="Copy", + style={"display": "inline-block"} + ), + title + ]), + dbc.CardBody( + id="tput-lat-metadata", + class_name="p-0", + children=[dbc.ListGroup( + [ + dbc.ListGroupItem([dbc.Badge(k), v]) + for k, v in _process_stats( + graph_data, trigger.idx) + ], + flush=True) + ] + ) + ] + ) + ] + + return metadata, graph, True + + @app.callback( + Output("offcanvas-documentation", "is_open"), + Input("btn-documentation", "n_clicks"), + State("offcanvas-documentation", "is_open") + ) + def toggle_offcanvas_documentation(n_clicks, is_open): + if n_clicks: + return not is_open + return is_open