UTI: Move common functions to utils.
[csit.git] / resources / tools / dash / app / pal / trending / graphs.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 """
15 """
16
17 import plotly.graph_objects as go
18 import pandas as pd
19
20 import hdrh.histogram
21 import hdrh.codec
22
23 from datetime import datetime
24
25 from ..utils.constants import Constants as C
26 from ..utils.utils import classify_anomalies, get_color
27
28
29 def _get_hdrh_latencies(row: pd.Series, name: str) -> dict:
30     """
31     """
32
33     latencies = {"name": name}
34     for key in C.LAT_HDRH:
35         try:
36             latencies[key] = row[key]
37         except KeyError:
38             return None
39
40     return latencies
41
42
43 def select_trending_data(data: pd.DataFrame, itm:dict) -> pd.DataFrame:
44     """
45     """
46
47     phy = itm["phy"].split("-")
48     if len(phy) == 4:
49         topo, arch, nic, drv = phy
50         if drv == "dpdk":
51             drv = ""
52         else:
53             drv += "-"
54             drv = drv.replace("_", "-")
55     else:
56         return None
57
58     core = str() if itm["dut"] == "trex" else f"{itm['core']}"
59     ttype = "ndrpdr" if itm["testtype"] in ("ndr", "pdr") else itm["testtype"]
60     dut_v100 = "none" if itm["dut"] == "trex" else itm["dut"]
61     dut_v101 = itm["dut"]
62
63     df = data.loc[(
64         (
65             (
66                 (data["version"] == "1.0.0") &
67                 (data["dut_type"].str.lower() == dut_v100)
68             ) |
69             (
70                 (data["version"] == "1.0.1") &
71                 (data["dut_type"].str.lower() == dut_v101)
72             )
73         ) &
74         (data["test_type"] == ttype) &
75         (data["passed"] == True)
76     )]
77     df = df[df.job.str.endswith(f"{topo}-{arch}")]
78     df = df[df.test_id.str.contains(
79         f"^.*[.|-]{nic}.*{itm['framesize']}-{core}-{drv}{itm['test']}-{ttype}$",
80         regex=True
81     )].sort_values(by="start_time", ignore_index=True)
82
83     return df
84
85
86 def _generate_trending_traces(ttype: str, name: str, df: pd.DataFrame,
87     start: datetime, end: datetime, color: str, norm_factor: float) -> list:
88     """
89     """
90
91     df = df.dropna(subset=[C.VALUE[ttype], ])
92     if df.empty:
93         return list()
94     df = df.loc[((df["start_time"] >= start) & (df["start_time"] <= end))]
95     if df.empty:
96         return list()
97
98     x_axis = df["start_time"].tolist()
99     if ttype == "pdr-lat":
100         y_data = [(itm / norm_factor) for itm in df[C.VALUE[ttype]].tolist()]
101     else:
102         y_data = [(itm * norm_factor) for itm in df[C.VALUE[ttype]].tolist()]
103
104     anomalies, trend_avg, trend_stdev = classify_anomalies(
105         {k: v for k, v in zip(x_axis, y_data)}
106     )
107
108     hover = list()
109     customdata = list()
110     for idx, (_, row) in enumerate(df.iterrows()):
111         d_type = "trex" if row["dut_type"] == "none" else row["dut_type"]
112         hover_itm = (
113             f"date: {row['start_time'].strftime('%Y-%m-%d %H:%M:%S')}<br>"
114             f"<prop> [{row[C.UNIT[ttype]]}]: {y_data[idx]:,.0f}<br>"
115             f"<stdev>"
116             f"{d_type}-ref: {row['dut_version']}<br>"
117             f"csit-ref: {row['job']}/{row['build']}<br>"
118             f"hosts: {', '.join(row['hosts'])}"
119         )
120         if ttype == "mrr":
121             stdev = (
122                 f"stdev [{row['result_receive_rate_rate_unit']}]: "
123                 f"{row['result_receive_rate_rate_stdev']:,.0f}<br>"
124             )
125         else:
126             stdev = ""
127         hover_itm = hover_itm.replace(
128             "<prop>", "latency" if ttype == "pdr-lat" else "average"
129         ).replace("<stdev>", stdev)
130         hover.append(hover_itm)
131         if ttype == "pdr-lat":
132             customdata.append(_get_hdrh_latencies(row, name))
133
134     hover_trend = list()
135     for avg, stdev, (_, row) in zip(trend_avg, trend_stdev, df.iterrows()):
136         d_type = "trex" if row["dut_type"] == "none" else row["dut_type"]
137         hover_itm = (
138             f"date: {row['start_time'].strftime('%Y-%m-%d %H:%M:%S')}<br>"
139             f"trend [pps]: {avg:,.0f}<br>"
140             f"stdev [pps]: {stdev:,.0f}<br>"
141             f"{d_type}-ref: {row['dut_version']}<br>"
142             f"csit-ref: {row['job']}/{row['build']}<br>"
143             f"hosts: {', '.join(row['hosts'])}"
144         )
145         if ttype == "pdr-lat":
146             hover_itm = hover_itm.replace("[pps]", "[us]")
147         hover_trend.append(hover_itm)
148
149     traces = [
150         go.Scatter(  # Samples
151             x=x_axis,
152             y=y_data,
153             name=name,
154             mode="markers",
155             marker={
156                 "size": 5,
157                 "color": color,
158                 "symbol": "circle",
159             },
160             text=hover,
161             hoverinfo="text+name",
162             showlegend=True,
163             legendgroup=name,
164             customdata=customdata
165         ),
166         go.Scatter(  # Trend line
167             x=x_axis,
168             y=trend_avg,
169             name=name,
170             mode="lines",
171             line={
172                 "shape": "linear",
173                 "width": 1,
174                 "color": color,
175             },
176             text=hover_trend,
177             hoverinfo="text+name",
178             showlegend=False,
179             legendgroup=name,
180         )
181     ]
182
183     if anomalies:
184         anomaly_x = list()
185         anomaly_y = list()
186         anomaly_color = list()
187         hover = list()
188         for idx, anomaly in enumerate(anomalies):
189             if anomaly in ("regression", "progression"):
190                 anomaly_x.append(x_axis[idx])
191                 anomaly_y.append(trend_avg[idx])
192                 anomaly_color.append(C.ANOMALY_COLOR[anomaly])
193                 hover_itm = (
194                     f"date: {x_axis[idx].strftime('%Y-%m-%d %H:%M:%S')}<br>"
195                     f"trend [pps]: {trend_avg[idx]:,.0f}<br>"
196                     f"classification: {anomaly}"
197                 )
198                 if ttype == "pdr-lat":
199                     hover_itm = hover_itm.replace("[pps]", "[us]")
200                 hover.append(hover_itm)
201         anomaly_color.extend([0.0, 0.5, 1.0])
202         traces.append(
203             go.Scatter(
204                 x=anomaly_x,
205                 y=anomaly_y,
206                 mode="markers",
207                 text=hover,
208                 hoverinfo="text+name",
209                 showlegend=False,
210                 legendgroup=name,
211                 name=name,
212                 marker={
213                     "size": 15,
214                     "symbol": "circle-open",
215                     "color": anomaly_color,
216                     "colorscale": C.COLORSCALE_LAT \
217                         if ttype == "pdr-lat" else C.COLORSCALE_TPUT,
218                     "showscale": True,
219                     "line": {
220                         "width": 2
221                     },
222                     "colorbar": {
223                         "y": 0.5,
224                         "len": 0.8,
225                         "title": "Circles Marking Data Classification",
226                         "titleside": "right",
227                         "tickmode": "array",
228                         "tickvals": [0.167, 0.500, 0.833],
229                         "ticktext": C.TICK_TEXT_LAT \
230                             if ttype == "pdr-lat" else C.TICK_TEXT_TPUT,
231                         "ticks": "",
232                         "ticklen": 0,
233                         "tickangle": -90,
234                         "thickness": 10
235                     }
236                 }
237             )
238         )
239
240     return traces
241
242
243 def graph_trending(data: pd.DataFrame, sel:dict, layout: dict,
244     start: datetime, end: datetime, normalize: bool) -> tuple:
245     """
246     """
247
248     if not sel:
249         return None, None
250
251     fig_tput = None
252     fig_lat = None
253     for idx, itm in enumerate(sel):
254
255         df = select_trending_data(data, itm)
256         if df is None or df.empty:
257             continue
258
259         name = "-".join((itm["dut"], itm["phy"], itm["framesize"], itm["core"],
260             itm["test"], itm["testtype"], ))
261         if normalize:
262             phy = itm["phy"].split("-")
263             topo_arch = f"{phy[0]}-{phy[1]}" if len(phy) == 4 else str()
264             norm_factor = (C.NORM_FREQUENCY / C.FREQUENCY[topo_arch]) \
265                 if topo_arch else 1.0
266         else:
267             norm_factor = 1.0
268         traces = _generate_trending_traces(
269             itm["testtype"], name, df, start, end, get_color(idx), norm_factor
270         )
271         if traces:
272             if not fig_tput:
273                 fig_tput = go.Figure()
274             fig_tput.add_traces(traces)
275
276         if itm["testtype"] == "pdr":
277             traces = _generate_trending_traces(
278                 "pdr-lat", name, df, start, end, get_color(idx), norm_factor
279             )
280             if traces:
281                 if not fig_lat:
282                     fig_lat = go.Figure()
283                 fig_lat.add_traces(traces)
284
285     if fig_tput:
286         fig_tput.update_layout(layout.get("plot-trending-tput", dict()))
287     if fig_lat:
288         fig_lat.update_layout(layout.get("plot-trending-lat", dict()))
289
290     return fig_tput, fig_lat
291
292
293 def graph_hdrh_latency(data: dict, layout: dict) -> go.Figure:
294     """
295     """
296
297     fig = None
298
299     traces = list()
300     for idx, (lat_name, lat_hdrh) in enumerate(data.items()):
301         try:
302             decoded = hdrh.histogram.HdrHistogram.decode(lat_hdrh)
303         except (hdrh.codec.HdrLengthException, TypeError) as err:
304             continue
305         previous_x = 0.0
306         prev_perc = 0.0
307         xaxis = list()
308         yaxis = list()
309         hovertext = list()
310         for item in decoded.get_recorded_iterator():
311             # The real value is "percentile".
312             # For 100%, we cut that down to "x_perc" to avoid
313             # infinity.
314             percentile = item.percentile_level_iterated_to
315             x_perc = min(percentile, C.PERCENTILE_MAX)
316             xaxis.append(previous_x)
317             yaxis.append(item.value_iterated_to)
318             hovertext.append(
319                 f"<b>{C.GRAPH_LAT_HDRH_DESC[lat_name]}</b><br>"
320                 f"Direction: {('W-E', 'E-W')[idx % 2]}<br>"
321                 f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
322                 f"Latency: {item.value_iterated_to}uSec"
323             )
324             next_x = 100.0 / (100.0 - x_perc)
325             xaxis.append(next_x)
326             yaxis.append(item.value_iterated_to)
327             hovertext.append(
328                 f"<b>{C.GRAPH_LAT_HDRH_DESC[lat_name]}</b><br>"
329                 f"Direction: {('W-E', 'E-W')[idx % 2]}<br>"
330                 f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
331                 f"Latency: {item.value_iterated_to}uSec"
332             )
333             previous_x = next_x
334             prev_perc = percentile
335
336         traces.append(
337             go.Scatter(
338                 x=xaxis,
339                 y=yaxis,
340                 name=C.GRAPH_LAT_HDRH_DESC[lat_name],
341                 mode="lines",
342                 legendgroup=C.GRAPH_LAT_HDRH_DESC[lat_name],
343                 showlegend=bool(idx % 2),
344                 line=dict(
345                     color=get_color(int(idx/2)),
346                     dash="solid",
347                     width=1 if idx % 2 else 2
348                 ),
349                 hovertext=hovertext,
350                 hoverinfo="text"
351             )
352         )
353     if traces:
354         fig = go.Figure()
355         fig.add_traces(traces)
356         layout_hdrh = layout.get("plot-hdrh-latency", None)
357         if lat_hdrh:
358             fig.update_layout(layout_hdrh)
359
360     return fig