UTI: Add Download for iterative data.
[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     """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     start: datetime, end: datetime, 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 start: The date (and time) when the selected data starts.
109     :param end: The date (and time) when the selected data ends.
110     :param color: The color of the trace (samples and trend line).
111     :param norm_factor: The factor used for normalization of the results to CPU
112         frequency set to Constants.NORM_FREQUENCY.
113     :type ttype: str
114     :type name: str
115     :type df: pandas.DataFrame
116     :type start: datetime.datetime
117     :type end: datetime.datetime
118     :type color: str
119     :type norm_factor: float
120     :returns: Traces (samples, trending line, anomalies)
121     :rtype: list
122     """
123
124     df = df.dropna(subset=[C.VALUE[ttype], ])
125     if df.empty:
126         return list()
127     df = df.loc[((df["start_time"] >= start) & (df["start_time"] <= end))]
128     if df.empty:
129         return list()
130
131     x_axis = df["start_time"].tolist()
132     if ttype == "pdr-lat":
133         y_data = [(itm / norm_factor) for itm in df[C.VALUE[ttype]].tolist()]
134     else:
135         y_data = [(itm * norm_factor) for itm in df[C.VALUE[ttype]].tolist()]
136
137     anomalies, trend_avg, trend_stdev = classify_anomalies(
138         {k: v for k, v in zip(x_axis, y_data)}
139     )
140
141     hover = list()
142     customdata = list()
143     for idx, (_, row) in enumerate(df.iterrows()):
144         d_type = "trex" if row["dut_type"] == "none" else row["dut_type"]
145         hover_itm = (
146             f"date: {row['start_time'].strftime('%Y-%m-%d %H:%M:%S')}<br>"
147             f"<prop> [{row[C.UNIT[ttype]]}]: {y_data[idx]:,.0f}<br>"
148             f"<stdev>"
149             f"{d_type}-ref: {row['dut_version']}<br>"
150             f"csit-ref: {row['job']}/{row['build']}<br>"
151             f"hosts: {', '.join(row['hosts'])}"
152         )
153         if ttype == "mrr":
154             stdev = (
155                 f"stdev [{row['result_receive_rate_rate_unit']}]: "
156                 f"{row['result_receive_rate_rate_stdev']:,.0f}<br>"
157             )
158         else:
159             stdev = ""
160         hover_itm = hover_itm.replace(
161             "<prop>", "latency" if ttype == "pdr-lat" else "average"
162         ).replace("<stdev>", stdev)
163         hover.append(hover_itm)
164         if ttype == "pdr-lat":
165             customdata.append(_get_hdrh_latencies(row, name))
166
167     hover_trend = list()
168     for avg, stdev, (_, row) in zip(trend_avg, trend_stdev, df.iterrows()):
169         d_type = "trex" if row["dut_type"] == "none" else row["dut_type"]
170         hover_itm = (
171             f"date: {row['start_time'].strftime('%Y-%m-%d %H:%M:%S')}<br>"
172             f"trend [pps]: {avg:,.0f}<br>"
173             f"stdev [pps]: {stdev:,.0f}<br>"
174             f"{d_type}-ref: {row['dut_version']}<br>"
175             f"csit-ref: {row['job']}/{row['build']}<br>"
176             f"hosts: {', '.join(row['hosts'])}"
177         )
178         if ttype == "pdr-lat":
179             hover_itm = hover_itm.replace("[pps]", "[us]")
180         hover_trend.append(hover_itm)
181
182     traces = [
183         go.Scatter(  # Samples
184             x=x_axis,
185             y=y_data,
186             name=name,
187             mode="markers",
188             marker={
189                 "size": 5,
190                 "color": color,
191                 "symbol": "circle",
192             },
193             text=hover,
194             hoverinfo="text+name",
195             showlegend=True,
196             legendgroup=name,
197             customdata=customdata
198         ),
199         go.Scatter(  # Trend line
200             x=x_axis,
201             y=trend_avg,
202             name=name,
203             mode="lines",
204             line={
205                 "shape": "linear",
206                 "width": 1,
207                 "color": color,
208             },
209             text=hover_trend,
210             hoverinfo="text+name",
211             showlegend=False,
212             legendgroup=name,
213         )
214     ]
215
216     if anomalies:
217         anomaly_x = list()
218         anomaly_y = list()
219         anomaly_color = list()
220         hover = list()
221         for idx, anomaly in enumerate(anomalies):
222             if anomaly in ("regression", "progression"):
223                 anomaly_x.append(x_axis[idx])
224                 anomaly_y.append(trend_avg[idx])
225                 anomaly_color.append(C.ANOMALY_COLOR[anomaly])
226                 hover_itm = (
227                     f"date: {x_axis[idx].strftime('%Y-%m-%d %H:%M:%S')}<br>"
228                     f"trend [pps]: {trend_avg[idx]:,.0f}<br>"
229                     f"classification: {anomaly}"
230                 )
231                 if ttype == "pdr-lat":
232                     hover_itm = hover_itm.replace("[pps]", "[us]")
233                 hover.append(hover_itm)
234         anomaly_color.extend([0.0, 0.5, 1.0])
235         traces.append(
236             go.Scatter(
237                 x=anomaly_x,
238                 y=anomaly_y,
239                 mode="markers",
240                 text=hover,
241                 hoverinfo="text+name",
242                 showlegend=False,
243                 legendgroup=name,
244                 name=name,
245                 marker={
246                     "size": 15,
247                     "symbol": "circle-open",
248                     "color": anomaly_color,
249                     "colorscale": C.COLORSCALE_LAT \
250                         if ttype == "pdr-lat" else C.COLORSCALE_TPUT,
251                     "showscale": True,
252                     "line": {
253                         "width": 2
254                     },
255                     "colorbar": {
256                         "y": 0.5,
257                         "len": 0.8,
258                         "title": "Circles Marking Data Classification",
259                         "titleside": "right",
260                         "tickmode": "array",
261                         "tickvals": [0.167, 0.500, 0.833],
262                         "ticktext": C.TICK_TEXT_LAT \
263                             if ttype == "pdr-lat" else C.TICK_TEXT_TPUT,
264                         "ticks": "",
265                         "ticklen": 0,
266                         "tickangle": -90,
267                         "thickness": 10
268                     }
269                 }
270             )
271         )
272
273     return traces
274
275
276 def graph_trending(data: pd.DataFrame, sel:dict, layout: dict,
277     start: datetime, end: datetime, normalize: bool) -> tuple:
278     """Generate the trending graph(s) - MRR, NDR, PDR and for PDR also Latences
279     (result_latency_forward_pdr_50_avg).
280
281     :param data: Data frame with test results.
282     :param sel: Selected tests.
283     :param layout: Layout of plot.ly graph.
284     :param start: The date (and time) when the selected data starts.
285     :param end: The date (and time) when the selected data ends.
286     :param normalize: If True, the data is normalized to CPU frquency
287         Constants.NORM_FREQUENCY.
288     :type data: pandas.DataFrame
289     :type sel: dict
290     :type layout: dict
291     :type start: datetime.datetime
292     :type end: datetype.datetype
293     :type normalize: bool
294     :returns: Trending graph(s)
295     :rtype: tuple(plotly.graph_objects.Figure, plotly.graph_objects.Figure)
296     """
297
298     if not sel:
299         return None, None
300
301     fig_tput = None
302     fig_lat = None
303     for idx, itm in enumerate(sel):
304
305         df = select_trending_data(data, itm)
306         if df is None or df.empty:
307             continue
308
309         name = "-".join((itm["dut"], itm["phy"], itm["framesize"], itm["core"],
310             itm["test"], itm["testtype"], ))
311         if normalize:
312             phy = itm["phy"].split("-")
313             topo_arch = f"{phy[0]}-{phy[1]}" if len(phy) == 4 else str()
314             norm_factor = (C.NORM_FREQUENCY / C.FREQUENCY[topo_arch]) \
315                 if topo_arch else 1.0
316         else:
317             norm_factor = 1.0
318         traces = _generate_trending_traces(
319             itm["testtype"], name, df, start, end, get_color(idx), norm_factor
320         )
321         if traces:
322             if not fig_tput:
323                 fig_tput = go.Figure()
324             fig_tput.add_traces(traces)
325
326         if itm["testtype"] == "pdr":
327             traces = _generate_trending_traces(
328                 "pdr-lat", name, df, start, end, get_color(idx), norm_factor
329             )
330             if traces:
331                 if not fig_lat:
332                     fig_lat = go.Figure()
333                 fig_lat.add_traces(traces)
334
335     if fig_tput:
336         fig_tput.update_layout(layout.get("plot-trending-tput", dict()))
337     if fig_lat:
338         fig_lat.update_layout(layout.get("plot-trending-lat", dict()))
339
340     return fig_tput, fig_lat
341
342
343 def graph_hdrh_latency(data: dict, layout: dict) -> go.Figure:
344     """Generate HDR Latency histogram graphs.
345
346     :param data: HDRH data.
347     :param layout: Layout of plot.ly graph.
348     :type data: dict
349     :type layout: dict
350     :returns: HDR latency Histogram.
351     :rtype: plotly.graph_objects.Figure
352     """
353
354     fig = None
355
356     traces = list()
357     for idx, (lat_name, lat_hdrh) in enumerate(data.items()):
358         try:
359             decoded = hdrh.histogram.HdrHistogram.decode(lat_hdrh)
360         except (hdrh.codec.HdrLengthException, TypeError) as err:
361             continue
362         previous_x = 0.0
363         prev_perc = 0.0
364         xaxis = list()
365         yaxis = list()
366         hovertext = list()
367         for item in decoded.get_recorded_iterator():
368             # The real value is "percentile".
369             # For 100%, we cut that down to "x_perc" to avoid
370             # infinity.
371             percentile = item.percentile_level_iterated_to
372             x_perc = min(percentile, C.PERCENTILE_MAX)
373             xaxis.append(previous_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             next_x = 100.0 / (100.0 - x_perc)
382             xaxis.append(next_x)
383             yaxis.append(item.value_iterated_to)
384             hovertext.append(
385                 f"<b>{C.GRAPH_LAT_HDRH_DESC[lat_name]}</b><br>"
386                 f"Direction: {('W-E', 'E-W')[idx % 2]}<br>"
387                 f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
388                 f"Latency: {item.value_iterated_to}uSec"
389             )
390             previous_x = next_x
391             prev_perc = percentile
392
393         traces.append(
394             go.Scatter(
395                 x=xaxis,
396                 y=yaxis,
397                 name=C.GRAPH_LAT_HDRH_DESC[lat_name],
398                 mode="lines",
399                 legendgroup=C.GRAPH_LAT_HDRH_DESC[lat_name],
400                 showlegend=bool(idx % 2),
401                 line=dict(
402                     color=get_color(int(idx/2)),
403                     dash="solid",
404                     width=1 if idx % 2 else 2
405                 ),
406                 hovertext=hovertext,
407                 hoverinfo="text"
408             )
409         )
410     if traces:
411         fig = go.Figure()
412         fig.add_traces(traces)
413         layout_hdrh = layout.get("plot-hdrh-latency", None)
414         if lat_hdrh:
415             fig.update_layout(layout_hdrh)
416
417     return fig