UTI: PoC - Add trending 11/35811/3
authorTibor Frank <tifrank@cisco.com>
Tue, 29 Mar 2022 12:18:52 +0000 (14:18 +0200)
committerTibor Frank <tifrank@cisco.com>
Wed, 30 Mar 2022 12:18:11 +0000 (14:18 +0200)
Change-Id: I77f201a662076aef7e0d6dffcd5f6c835ee10e99
Signed-off-by: Tibor Frank <tifrank@cisco.com>
resources/tools/dash/app/pal/trending/graphs.py
resources/tools/dash/app/pal/trending/layout.py
resources/tools/dash/docker-compose.yaml

index a20ce8e..da528a9 100644 (file)
 """
 
 
+import logging
 import plotly.graph_objects as go
 import pandas as pd
 import re
 
 from datetime import datetime
-
+from numpy import isnan
 from dash import no_update
 
+from ..jumpavg import classify
+
+
+_COLORS = (
+    u"#1A1110",
+    u"#DA2647",
+    u"#214FC6",
+    u"#01786F",
+    u"#BD8260",
+    u"#FFD12A",
+    u"#A6E7FF",
+    u"#738276",
+    u"#C95A49",
+    u"#FC5A8D",
+    u"#CEC8EF",
+    u"#391285",
+    u"#6F2DA8",
+    u"#FF878D",
+    u"#45A27D",
+    u"#FFD0B9",
+    u"#FD5240",
+    u"#DB91EF",
+    u"#44D7A8",
+    u"#4F86F7",
+    u"#84DE02",
+    u"#FFCFF1",
+    u"#614051"
+)
+_ANOMALY_COLOR = {
+    u"regression": 0.0,
+    u"normal": 0.5,
+    u"progression": 1.0
+}
+_COLORSCALE = [
+    [0.00, u"red"],
+    [0.33, u"red"],
+    [0.33, u"white"],
+    [0.66, u"white"],
+    [0.66, u"green"],
+    [1.00, u"green"]
+]
+_VALUE = {
+    "mrr": "result_receive_rate_rate_avg",
+    "ndr": "result_ndr_lower_rate_value",
+    "pdr": "result_pdr_lower_rate_value"
+}
+_UNIT = {
+    "mrr": "result_receive_rate_rate_unit",
+    "ndr": "result_ndr_lower_rate_unit",
+    "pdr": "result_pdr_lower_rate_unit"
+}
+
+
+def _classify_anomalies(data):
+    """Process the data and return anomalies and trending values.
+
+    Gather data into groups with average as trend value.
+    Decorate values within groups to be normal,
+    the first value of changed average as a regression, or a progression.
+
+    :param data: Full data set with unavailable samples replaced by nan.
+    :type data: OrderedDict
+    :returns: Classification and trend values
+    :rtype: 3-tuple, list of strings, list of floats and list of floats
+    """
+    # NaN means something went wrong.
+    # Use 0.0 to cause that being reported as a severe regression.
+    bare_data = [0.0 if isnan(sample) else sample for sample in data.values()]
+    # TODO: Make BitCountingGroupList a subclass of list again?
+    group_list = classify(bare_data).group_list
+    group_list.reverse()  # Just to use .pop() for FIFO.
+    classification = list()
+    avgs = list()
+    stdevs = list()
+    active_group = None
+    values_left = 0
+    avg = 0.0
+    stdv = 0.0
+    for sample in data.values():
+        if isnan(sample):
+            classification.append(u"outlier")
+            avgs.append(sample)
+            stdevs.append(sample)
+            continue
+        if values_left < 1 or active_group is None:
+            values_left = 0
+            while values_left < 1:  # Ignore empty groups (should not happen).
+                active_group = group_list.pop()
+                values_left = len(active_group.run_list)
+            avg = active_group.stats.avg
+            stdv = active_group.stats.stdev
+            classification.append(active_group.comment)
+            avgs.append(avg)
+            stdevs.append(stdv)
+            values_left -= 1
+            continue
+        classification.append(u"normal")
+        avgs.append(avg)
+        stdevs.append(stdv)
+        values_left -= 1
+    return classification, avgs, stdevs
+
 
 def trending_tput(data: pd.DataFrame, sel:dict, layout: dict, start: datetime,
     end: datetime):
@@ -32,30 +135,26 @@ def trending_tput(data: pd.DataFrame, sel:dict, layout: dict, start: datetime,
     if not sel:
         return no_update, no_update
 
-    def _generate_trace(ttype: str, name: str, df: pd.DataFrame,
-        start: datetime, end: datetime):
-
-        value = {
-            "mrr": "result_receive_rate_rate_avg",
-            "ndr": "result_ndr_lower_rate_value",
-            "pdr": "result_pdr_lower_rate_value"
-        }
-        unit = {
-            "mrr": "result_receive_rate_rate_unit",
-            "ndr": "result_ndr_lower_rate_unit",
-            "pdr": "result_pdr_lower_rate_unit"
-        }
-
-        x_axis = [
-            d for d in df["start_time"] if d >= start and d <= end
-        ]
-        hover_txt = list()
+    def _generate_traces(ttype: str, name: str, df: pd.DataFrame,
+        start: datetime, end: datetime, color: str):
+
+        df = df.dropna(subset=[_VALUE[ttype], ])
+        if df.empty:
+            return list()
+
+        x_axis = [d for d in df["start_time"] if d >= start and d <= end]
+
+        anomalies, trend_avg, trend_stdev = _classify_anomalies(
+            {k: v for k, v in zip(x_axis, df[_VALUE[ttype]])}
+        )
+
+        hover = list()
         for _, row in df.iterrows():
             hover_itm = (
                 f"date: "
                 f"{row['start_time'].strftime('%d-%m-%Y %H:%M:%S')}<br>"
-                f"average [{row[unit[ttype]]}]: "
-                f"{row[value[ttype]]}<br>"
+                f"average [{row[_UNIT[ttype]]}]: "
+                f"{row[_VALUE[ttype]]}<br>"
                 f"{row['dut_type']}-ref: {row['dut_version']}<br>"
                 f"csit-ref: {row['job']}/{row['build']}"
             )
@@ -67,20 +166,102 @@ def trending_tput(data: pd.DataFrame, sel:dict, layout: dict, start: datetime,
             else:
                 stdev = ""
             hover_itm = hover_itm.replace("<stdev>", stdev)
-            hover_txt.append(hover_itm)
-
-        return go.Scatter(
-            x=x_axis,
-            y=df[value[ttype]],
-            name=name,
-            mode="markers+lines",
-            text=hover_txt,
-            hoverinfo=u"text+name"
-        )
+            hover.append(hover_itm)
+
+        hover_trend = list()
+        for avg, stdev in zip(trend_avg, trend_stdev):
+            hover_trend.append(
+                f"trend [pps]: {avg}<br>"
+                f"stdev [pps]: {stdev}"
+            )
+
+        traces = [
+            go.Scatter(  # Samples
+                x=x_axis,
+                y=df[_VALUE[ttype]],
+                name=name,
+                mode="markers",
+                marker={
+                    u"size": 5,
+                    u"color": color,
+                    u"symbol": u"circle",
+                },
+                text=hover,
+                hoverinfo=u"text+name",
+                showlegend=True,
+                legendgroup=name,
+            ),
+            go.Scatter(  # Trend line
+                x=x_axis,
+                y=trend_avg,
+                name=name,
+                mode="lines",
+                line={
+                    u"shape": u"linear",
+                    u"width": 1,
+                    u"color": color,
+                },
+                text=hover_trend,
+                hoverinfo=u"text+name",
+                showlegend=False,
+                legendgroup=name,
+            )
+        ]
+
+        if anomalies:
+            anomaly_x = list()
+            anomaly_y = list()
+            anomaly_color = list()
+            ticktext = [u"Regression", u"Normal", u"Progression"]
+            for idx, anomaly in enumerate(anomalies):
+                if anomaly in (u"regression", u"progression"):
+                    anomaly_x.append(x_axis[idx])
+                    anomaly_y.append(trend_avg[idx])
+                    anomaly_color.append(_ANOMALY_COLOR[anomaly])
+            anomaly_color.append([0.0, 1.0])
+            traces.append(
+                go.Scatter(
+                    x=anomaly_x,
+                    y=anomaly_y,
+                    mode=u"markers",
+                    hoverinfo=u"none",
+                    showlegend=False,
+                    legendgroup=name,
+                    name=f"{name}-anomalies",
+                    marker={
+                        u"size": 15,
+                        u"symbol": u"circle-open",
+                        u"color": anomaly_color,
+                        u"colorscale": _COLORSCALE,
+                        u"showscale": True,
+                        u"line": {
+                            u"width": 2
+                        },
+                        u"colorbar": {
+                            u"y": 0.5,
+                            u"len": 0.8,
+                            u"title": u"Circles Marking Data Classification",
+                            u"titleside": u"right",
+                            u"titlefont": {
+                                u"size": 14
+                            },
+                            u"tickmode": u"array",
+                            u"tickvals": [0.167, 0.500, 0.833],
+                            u"ticktext": ticktext,
+                            u"ticks": u"",
+                            u"ticklen": 0,
+                            u"tickangle": -90,
+                            u"thickness": 10
+                        }
+                    }
+                )
+            )
+
+        return traces
 
     # Generate graph:
     fig = go.Figure()
-    for itm in sel:
+    for idx, itm in enumerate(sel):
         phy = itm["phy"].split("-")
         if len(phy) == 4:
             topo, arch, nic, drv = phy
@@ -88,6 +269,7 @@ def trending_tput(data: pd.DataFrame, sel:dict, layout: dict, start: datetime,
                 drv = ""
             else:
                 drv += "-"
+                drv = drv.replace("_", "-")
         else:
             continue
         cadence = \
@@ -111,7 +293,9 @@ def trending_tput(data: pd.DataFrame, sel:dict, layout: dict, start: datetime,
             f"{itm['phy']}-{itm['framesize']}-{itm['core']}-"
             f"{itm['test']}-{itm['testtype']}"
         )
-        fig.add_trace(_generate_trace(itm['testtype'], name, df, start, end))
+        for trace in _generate_traces(itm['testtype'], name, df, start, end,
+                _COLORS[idx % len(_COLORS)]):
+            fig.add_trace(trace)
 
     style={
         "vertical-align": "top",
index 081f977..6369a02 100644 (file)
@@ -520,7 +520,7 @@ class Layout:
                         for framesize in framesizes:
                             for ttype in testtypes:
                                 tid = (
-                                    f"{phy}-"
+                                    f"{phy.replace('af_xdp', 'af-xdp')}-"
                                     f"{area}-"
                                     f"{framesize.lower()}-"
                                     f"{core.lower()}-"
index 155678a..7125097 100644 (file)
@@ -10,5 +10,6 @@ services:
       - "5000:5000"
     volumes:
       - "./app/:/app"
+      - "../../libraries/python/jumpavg/:/app/pal/jumpavg"
       - "/home/vagrant/.aws:/root/.aws"
     working_dir: "/app"