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