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