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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
19 import plotly.graph_objects as go
23 from datetime import datetime
24 from numpy import isnan
25 from dash import no_update
27 from ..jumpavg import classify
69 "mrr": "result_receive_rate_rate_avg",
70 "ndr": "result_ndr_lower_rate_value",
71 "pdr": "result_pdr_lower_rate_value"
74 "mrr": "result_receive_rate_rate_unit",
75 "ndr": "result_ndr_lower_rate_unit",
76 "pdr": "result_pdr_lower_rate_unit"
80 def _classify_anomalies(data):
81 """Process the data and return anomalies and trending values.
83 Gather data into groups with average as trend value.
84 Decorate values within groups to be normal,
85 the first value of changed average as a regression, or a progression.
87 :param data: Full data set with unavailable samples replaced by nan.
88 :type data: OrderedDict
89 :returns: Classification and trend values
90 :rtype: 3-tuple, list of strings, list of floats and list of floats
92 # NaN means something went wrong.
93 # Use 0.0 to cause that being reported as a severe regression.
94 bare_data = [0.0 if isnan(sample) else sample for sample in data.values()]
95 # TODO: Make BitCountingGroupList a subclass of list again?
96 group_list = classify(bare_data).group_list
97 group_list.reverse() # Just to use .pop() for FIFO.
98 classification = list()
105 for sample in data.values():
107 classification.append(u"outlier")
109 stdevs.append(sample)
111 if values_left < 1 or active_group is None:
113 while values_left < 1: # Ignore empty groups (should not happen).
114 active_group = group_list.pop()
115 values_left = len(active_group.run_list)
116 avg = active_group.stats.avg
117 stdv = active_group.stats.stdev
118 classification.append(active_group.comment)
123 classification.append(u"normal")
127 return classification, avgs, stdevs
130 def trending_tput(data: pd.DataFrame, sel:dict, layout: dict, start: datetime,
136 return no_update, no_update
138 def _generate_traces(ttype: str, name: str, df: pd.DataFrame,
139 start: datetime, end: datetime, color: str):
141 df = df.dropna(subset=[_VALUE[ttype], ])
145 x_axis = [d for d in df["start_time"] if d >= start and d <= end]
147 anomalies, trend_avg, trend_stdev = _classify_anomalies(
148 {k: v for k, v in zip(x_axis, df[_VALUE[ttype]])}
152 for _, row in df.iterrows():
155 f"{row['start_time'].strftime('%d-%m-%Y %H:%M:%S')}<br>"
156 f"average [{row[_UNIT[ttype]]}]: "
157 f"{row[_VALUE[ttype]]}<br>"
158 f"{row['dut_type']}-ref: {row['dut_version']}<br>"
159 f"csit-ref: {row['job']}/{row['build']}"
163 f"stdev [{row['result_receive_rate_rate_unit']}]: "
164 f"{row['result_receive_rate_rate_stdev']}<br>"
168 hover_itm = hover_itm.replace("<stdev>", stdev)
169 hover.append(hover_itm)
172 for avg, stdev in zip(trend_avg, trend_stdev):
174 f"trend [pps]: {avg}<br>"
175 f"stdev [pps]: {stdev}"
179 go.Scatter( # Samples
187 u"symbol": u"circle",
190 hoverinfo=u"text+name",
194 go.Scatter( # Trend line
205 hoverinfo=u"text+name",
214 anomaly_color = list()
215 ticktext = [u"Regression", u"Normal", u"Progression"]
216 for idx, anomaly in enumerate(anomalies):
217 if anomaly in (u"regression", u"progression"):
218 anomaly_x.append(x_axis[idx])
219 anomaly_y.append(trend_avg[idx])
220 anomaly_color.append(_ANOMALY_COLOR[anomaly])
221 anomaly_color.append([0.0, 0.5, 1.0])
230 name=f"{name}-anomalies",
233 u"symbol": u"circle-open",
234 u"color": anomaly_color,
235 u"colorscale": _COLORSCALE,
243 u"title": u"Circles Marking Data Classification",
244 u"titleside": u"right",
248 u"tickmode": u"array",
249 u"tickvals": [0.167, 0.500, 0.833],
250 u"ticktext": ticktext,
264 for idx, itm in enumerate(sel):
265 phy = itm["phy"].split("-")
267 topo, arch, nic, drv = phy
268 if drv in ("dpdk", "ixgbe"):
272 drv = drv.replace("_", "-")
276 "weekly" if (arch == "aws" or itm["testtype"] != "mrr") else "daily"
279 f"{itm['testtype'] if itm['testtype'] == 'mrr' else 'ndrpdr'}-"
280 f"{cadence}-master-{topo}-{arch}"
282 df_sel = data.loc[(data["job"] == sel_topo_arch)]
284 f"^.*{nic}.*\.{itm['framesize']}-{itm['core']}-{drv}{itm['test']}-"
285 f"{'mrr' if itm['testtype'] == 'mrr' else 'ndrpdr'}$"
288 df_sel["test_id"].apply(
289 lambda x: True if re.search(regex, x) else False
291 ].sort_values(by="start_time", ignore_index=True)
293 f"{itm['phy']}-{itm['framesize']}-{itm['core']}-"
294 f"{itm['test']}-{itm['testtype']}"
296 for trace in _generate_traces(itm['testtype'], name, df, start, end,
297 _COLORS[idx % len(_COLORS)]):
301 "vertical-align": "top",
302 "display": "inline-block",
307 layout = layout.get("plot-trending", dict())
308 fig.update_layout(layout)