C-Dash: Add latency hdrh for iterative data
[csit.git] / csit.infra.dash / app / cdash / utils / utils.py
index 58bdb05..d9347b1 100644 (file)
@@ -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:
 """
 
 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
 from datetime import datetime
@@ -346,29 +351,172 @@ def set_job_params(df: pd.DataFrame, job: str) -> dict:
     }
 
 
-def get_list_group_items(tests: list) -> list:
-    """Generate list of ListGroupItems with checkboxes with selected tests.
-
-    :param tests: List of tests to be displayed in the ListGroup.
-    :type tests: list
-    :returns: List of ListGroupItems with checkboxes with selected tests.
+def get_list_group_items(
+        items: list,
+        type: str,
+        colorize: bool=True,
+        add_index: bool=False
+    ) -> list:
+    """Generate list of ListGroupItems with checkboxes with selected items.
+
+    :param items: List of items to be displayed in the ListGroup.
+    :param type: The type part of an element ID.
+    :param colorize: If True, the color of labels is set, otherwise the default
+        color is used.
+    :param add_index: Add index to the list items.
+    :type items: list
+    :type type: str
+    :type colorize: bool
+    :type add_index: bool
+    :returns: List of ListGroupItems with checkboxes with selected items.
     :rtype: list
     """
-    return [
-        dbc.ListGroupItem(
-            children=[
-                dbc.Checkbox(
-                    id={"type": "sel-cl", "index": i},
-                    label=l["id"],
-                    value=False,
-                    label_class_name="m-0 p-0",
-                    label_style={
-                        "font-size": ".875em",
-                        "color": get_color(i)
-                    },
-                    class_name="info"
-                )
-            ],
-            class_name="p-0"
-        ) for i, l in enumerate(tests)
-    ]
+
+    children = list()
+    for i, l in enumerate(items):
+        idx = f"{i + 1}. " if add_index else str()
+        label = f"{idx}{l['id']}" if isinstance(l, dict) else f"{idx}{l}"
+        children.append(
+            dbc.ListGroupItem(
+                children=[
+                    dbc.Checkbox(
+                        id={"type": type, "index": i},
+                        label=label,
+                        value=False,
+                        label_class_name="m-0 p-0",
+                        label_style={
+                            "font-size": ".875em",
+                            "color": get_color(i) if colorize else "#55595c"
+                        },
+                        class_name="info"
+                    )
+                ],
+                class_name="p-0"
+            )
+        )
+
+    return children
+
+
+def relative_change_stdev(mean1, mean2, std1, std2):
+    """Compute relative standard deviation of change of two values.
+
+    The "1" values are the base for comparison.
+    Results are returned as percentage (and percentual points for stdev).
+    Linearized theory is used, so results are wrong for relatively large stdev.
+
+    :param mean1: Mean of the first number.
+    :param mean2: Mean of the second number.
+    :param std1: Standard deviation estimate of the first number.
+    :param std2: Standard deviation estimate of the second number.
+    :type mean1: float
+    :type mean2: float
+    :type std1: float
+    :type std2: float
+    :returns: Relative change and its stdev.
+    :rtype: float
+    """
+    mean1, mean2 = float(mean1), float(mean2)
+    quotient = mean2 / mean1
+    first = std1 / mean1
+    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