diff options
-rw-r--r-- | csit.infra.dash/app/cdash/data/data.yaml | 24 | ||||
-rw-r--r-- | csit.infra.dash/app/cdash/report/graphs.py | 11 | ||||
-rw-r--r-- | csit.infra.dash/app/cdash/report/layout.py | 125 | ||||
-rw-r--r-- | csit.infra.dash/app/cdash/trending/graphs.py | 105 | ||||
-rw-r--r-- | csit.infra.dash/app/cdash/trending/layout.py | 5 | ||||
-rw-r--r-- | csit.infra.dash/app/cdash/utils/utils.py | 103 |
6 files changed, 264 insertions, 109 deletions
diff --git a/csit.infra.dash/app/cdash/data/data.yaml b/csit.infra.dash/app/cdash/data/data.yaml index 7a03e13d08..4e78323fe7 100644 --- a/csit.infra.dash/app/cdash/data/data.yaml +++ b/csit.infra.dash/app/cdash/data/data.yaml @@ -193,8 +193,16 @@ - result_pdr_lower_rate_value - result_ndr_lower_rate_unit - result_ndr_lower_rate_value + - result_latency_reverse_pdr_90_hdrh + - result_latency_reverse_pdr_50_hdrh + - result_latency_reverse_pdr_10_hdrh + - result_latency_reverse_pdr_0_hdrh + - result_latency_forward_pdr_90_hdrh - result_latency_forward_pdr_50_avg + - result_latency_forward_pdr_50_hdrh - result_latency_forward_pdr_50_unit + - result_latency_forward_pdr_10_hdrh + - result_latency_forward_pdr_0_hdrh categories: - job - build @@ -220,8 +228,16 @@ - result_pdr_lower_rate_value - result_ndr_lower_rate_unit - result_ndr_lower_rate_value + - result_latency_reverse_pdr_90_hdrh + - result_latency_reverse_pdr_50_hdrh + - result_latency_reverse_pdr_10_hdrh + - result_latency_reverse_pdr_0_hdrh + - result_latency_forward_pdr_90_hdrh - result_latency_forward_pdr_50_avg + - result_latency_forward_pdr_50_hdrh - result_latency_forward_pdr_50_unit + - result_latency_forward_pdr_10_hdrh + - result_latency_forward_pdr_0_hdrh categories: - job - build @@ -247,8 +263,16 @@ - result_pdr_lower_rate_value - result_ndr_lower_rate_unit - result_ndr_lower_rate_value + - result_latency_reverse_pdr_90_hdrh + - result_latency_reverse_pdr_50_hdrh + - result_latency_reverse_pdr_10_hdrh + - result_latency_reverse_pdr_0_hdrh + - result_latency_forward_pdr_90_hdrh - result_latency_forward_pdr_50_avg + - result_latency_forward_pdr_50_hdrh - result_latency_forward_pdr_50_unit + - result_latency_forward_pdr_10_hdrh + - result_latency_forward_pdr_0_hdrh categories: - job - build diff --git a/csit.infra.dash/app/cdash/report/graphs.py b/csit.infra.dash/app/cdash/report/graphs.py index 544fdf0b54..f45e17ff33 100644 --- a/csit.infra.dash/app/cdash/report/graphs.py +++ b/csit.infra.dash/app/cdash/report/graphs.py @@ -20,7 +20,7 @@ import pandas as pd from copy import deepcopy from ..utils.constants import Constants as C -from ..utils.utils import get_color +from ..utils.utils import get_color, get_hdrh_latencies def select_iterative_data(data: pd.DataFrame, itm:dict) -> pd.DataFrame: @@ -145,6 +145,12 @@ def graph_iterative(data: pd.DataFrame, sel:dict, layout: dict, show_tput = True if ttype == "pdr": + customdata = list() + for _, row in itm_data.iterrows(): + customdata.append( + get_hdrh_latencies(row, f"{row['job']}/{row['build']}") + ) + y_lat_row = itm_data[C.VALUE_ITER["latency"]].to_list() y_lat = [(y / norm_factor) for y in y_lat_row] if y_lat: @@ -164,7 +170,8 @@ def graph_iterative(data: pd.DataFrame, sel:dict, layout: dict, hoverinfo="all", boxpoints="all", jitter=0.3, - marker=dict(color=get_color(idx)) + marker=dict(color=get_color(idx)), + customdata=customdata ) x_lat.append(idx + 1) lat_traces.append(go.Box(**lat_kwargs)) diff --git a/csit.infra.dash/app/cdash/report/layout.py b/csit.infra.dash/app/cdash/report/layout.py index 2f954b611b..8dbaea3508 100644 --- a/csit.infra.dash/app/cdash/report/layout.py +++ b/csit.infra.dash/app/cdash/report/layout.py @@ -31,7 +31,7 @@ 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 .graphs import graph_iterative, select_iterative_data @@ -279,6 +279,20 @@ 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 ) ] ) @@ -1320,3 +1334,112 @@ 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 diff --git a/csit.infra.dash/app/cdash/trending/graphs.py b/csit.infra.dash/app/cdash/trending/graphs.py index 10ad745e1e..fc26f8bd79 100644 --- a/csit.infra.dash/app/cdash/trending/graphs.py +++ b/csit.infra.dash/app/cdash/trending/graphs.py @@ -17,32 +17,8 @@ import plotly.graph_objects as go import pandas as pd -import hdrh.histogram -import hdrh.codec - from ..utils.constants import Constants as C -from ..utils.utils import classify_anomalies, get_color - - -def _get_hdrh_latencies(row: pd.Series, name: str) -> dict: - """Get the HDRH latencies from the test data. - - :param row: A row fron the data frame with test data. - :param name: The test name to be displayed as the graph title. - :type row: pandas.Series - :type name: str - :returns: Dictionary with HDRH latencies. - :rtype: dict - """ - - latencies = {"name": name} - for key in C.LAT_HDRH: - try: - latencies[key] = row[key] - except KeyError: - return None - - return latencies +from ..utils.utils import classify_anomalies, get_color, get_hdrh_latencies def select_trending_data(data: pd.DataFrame, itm: dict) -> pd.DataFrame: @@ -189,7 +165,7 @@ def graph_trending( ).replace("<stdev>", stdev).replace("<additional-info>", add_info) hover.append(hover_itm) if ttype == "latency": - customdata_samples.append(_get_hdrh_latencies(row, name)) + customdata_samples.append(get_hdrh_latencies(row, name)) customdata.append({"name": name}) else: customdata_samples.append( @@ -367,83 +343,6 @@ def graph_trending( return fig_tput, fig_lat -def graph_hdrh_latency(data: dict, layout: dict) -> go.Figure: - """Generate HDR Latency histogram graphs. - - :param data: HDRH data. - :param layout: Layout of plot.ly graph. - :type data: dict - :type layout: dict - :returns: HDR latency Histogram. - :rtype: plotly.graph_objects.Figure - """ - - fig = None - - traces = list() - for idx, (lat_name, lat_hdrh) in enumerate(data.items()): - try: - decoded = hdrh.histogram.HdrHistogram.decode(lat_hdrh) - except (hdrh.codec.HdrLengthException, TypeError): - continue - previous_x = 0.0 - prev_perc = 0.0 - xaxis = list() - yaxis = list() - hovertext = list() - for item in decoded.get_recorded_iterator(): - # The real value is "percentile". - # For 100%, we cut that down to "x_perc" to avoid - # infinity. - percentile = item.percentile_level_iterated_to - x_perc = min(percentile, C.PERCENTILE_MAX) - xaxis.append(previous_x) - yaxis.append(item.value_iterated_to) - hovertext.append( - f"<b>{C.GRAPH_LAT_HDRH_DESC[lat_name]}</b><br>" - f"Direction: {('W-E', 'E-W')[idx % 2]}<br>" - f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>" - f"Latency: {item.value_iterated_to}uSec" - ) - next_x = 100.0 / (100.0 - x_perc) - xaxis.append(next_x) - yaxis.append(item.value_iterated_to) - hovertext.append( - f"<b>{C.GRAPH_LAT_HDRH_DESC[lat_name]}</b><br>" - f"Direction: {('W-E', 'E-W')[idx % 2]}<br>" - f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>" - f"Latency: {item.value_iterated_to}uSec" - ) - previous_x = next_x - prev_perc = percentile - - traces.append( - go.Scatter( - x=xaxis, - y=yaxis, - name=C.GRAPH_LAT_HDRH_DESC[lat_name], - mode="lines", - legendgroup=C.GRAPH_LAT_HDRH_DESC[lat_name], - showlegend=bool(idx % 2), - line=dict( - color=get_color(int(idx/2)), - dash="solid", - width=1 if idx % 2 else 2 - ), - hovertext=hovertext, - hoverinfo="text" - ) - ) - if traces: - fig = go.Figure() - fig.add_traces(traces) - layout_hdrh = layout.get("plot-hdrh-latency", None) - if lat_hdrh: - fig.update_layout(layout_hdrh) - - return fig - - def graph_tm_trending(data: pd.DataFrame, layout: dict) -> list: """Generates one trending graph per test, each graph includes all selected metrics. diff --git a/csit.infra.dash/app/cdash/trending/layout.py b/csit.infra.dash/app/cdash/trending/layout.py index e4b7094de3..411061470e 100644 --- a/csit.infra.dash/app/cdash/trending/layout.py +++ b/csit.infra.dash/app/cdash/trending/layout.py @@ -34,10 +34,9 @@ from ..utils.control_panel import ControlPanel from ..utils.trigger import Trigger from ..utils.telemetry_data import TelemetryData 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 .graphs import graph_trending, graph_hdrh_latency, select_trending_data, \ - graph_tm_trending +from .graphs import graph_trending, select_trending_data, graph_tm_trending # Control panel partameters and their default values. diff --git a/csit.infra.dash/app/cdash/utils/utils.py b/csit.infra.dash/app/cdash/utils/utils.py index 68099987d2..d9347b1c13 100644 --- a/csit.infra.dash/app/cdash/utils/utils.py +++ b/csit.infra.dash/app/cdash/utils/utils.py @@ -15,8 +15,12 @@ """ import pandas as pd +import plotly.graph_objects as go import dash_bootstrap_components as dbc +import hdrh.histogram +import hdrh.codec + from math import sqrt from numpy import isnan from dash import dcc @@ -393,6 +397,7 @@ def get_list_group_items( return children + def relative_change_stdev(mean1, mean2, std1, std2): """Compute relative standard deviation of change of two values. @@ -417,3 +422,101 @@ def relative_change_stdev(mean1, mean2, std1, std2): second = std2 / mean2 std = quotient * sqrt(first * first + second * second) return (quotient - 1) * 100, std * 100 + + +def get_hdrh_latencies(row: pd.Series, name: str) -> dict: + """Get the HDRH latencies from the test data. + + :param row: A row fron the data frame with test data. + :param name: The test name to be displayed as the graph title. + :type row: pandas.Series + :type name: str + :returns: Dictionary with HDRH latencies. + :rtype: dict + """ + + latencies = {"name": name} + for key in C.LAT_HDRH: + try: + latencies[key] = row[key] + except KeyError: + return None + + return latencies + + +def graph_hdrh_latency(data: dict, layout: dict) -> go.Figure: + """Generate HDR Latency histogram graphs. + + :param data: HDRH data. + :param layout: Layout of plot.ly graph. + :type data: dict + :type layout: dict + :returns: HDR latency Histogram. + :rtype: plotly.graph_objects.Figure + """ + + fig = None + + traces = list() + for idx, (lat_name, lat_hdrh) in enumerate(data.items()): + try: + decoded = hdrh.histogram.HdrHistogram.decode(lat_hdrh) + except (hdrh.codec.HdrLengthException, TypeError): + continue + previous_x = 0.0 + prev_perc = 0.0 + xaxis = list() + yaxis = list() + hovertext = list() + for item in decoded.get_recorded_iterator(): + # The real value is "percentile". + # For 100%, we cut that down to "x_perc" to avoid + # infinity. + percentile = item.percentile_level_iterated_to + x_perc = min(percentile, C.PERCENTILE_MAX) + xaxis.append(previous_x) + yaxis.append(item.value_iterated_to) + hovertext.append( + f"<b>{C.GRAPH_LAT_HDRH_DESC[lat_name]}</b><br>" + f"Direction: {('W-E', 'E-W')[idx % 2]}<br>" + f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>" + f"Latency: {item.value_iterated_to}uSec" + ) + next_x = 100.0 / (100.0 - x_perc) + xaxis.append(next_x) + yaxis.append(item.value_iterated_to) + hovertext.append( + f"<b>{C.GRAPH_LAT_HDRH_DESC[lat_name]}</b><br>" + f"Direction: {('W-E', 'E-W')[idx % 2]}<br>" + f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>" + f"Latency: {item.value_iterated_to}uSec" + ) + previous_x = next_x + prev_perc = percentile + + traces.append( + go.Scatter( + x=xaxis, + y=yaxis, + name=C.GRAPH_LAT_HDRH_DESC[lat_name], + mode="lines", + legendgroup=C.GRAPH_LAT_HDRH_DESC[lat_name], + showlegend=bool(idx % 2), + line=dict( + color=get_color(int(idx/2)), + dash="solid", + width=1 if idx % 2 else 2 + ), + hovertext=hovertext, + hoverinfo="text" + ) + ) + if traces: + fig = go.Figure() + fig.add_traces(traces) + layout_hdrh = layout.get("plot-hdrh-latency", None) + if lat_hdrh: + fig.update_layout(layout_hdrh) + + return fig |