6cc29566374f3486f851a27d8cede7e270ae9602
[csit.git] / csit.infra.dash / app / cdash / coverage / tables.py
1 # Copyright (c) 2023 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 """The coverage data tables.
15 """
16
17 import hdrh.histogram
18 import hdrh.codec
19 import pandas as pd
20 import dash_bootstrap_components as dbc
21
22 from dash import dash_table
23 from dash.dash_table.Format import Format, Scheme
24
25 from ..utils.constants import Constants as C
26
27
28 def select_coverage_data(
29         data: pd.DataFrame,
30         selected: dict,
31         csv: bool=False,
32         show_latency: bool=True
33     ) -> list:
34     """Select coverage data for the tables and generate tables as pandas data
35     frames.
36
37     :param data: Coverage data.
38     :param selected: Dictionary with user selection.
39     :param csv: If True, pandas data frame with selected coverage data is
40         returned for "Download Data" feature.
41     :param show_latency: If True, latency is displayed in the tables.
42     :type data: pandas.DataFrame
43     :type selected: dict
44     :type csv: bool
45     :type show_latency: bool
46     :returns: List of tuples with suite name (str) and data (pandas dataframe)
47         or pandas dataframe if csv is True.
48     :rtype: list[tuple[str, pandas.DataFrame], ] or pandas.DataFrame
49     """
50
51     l_data = list()
52
53     # Filter data selected by the user.
54     phy = selected["phy"].split("-")
55     if len(phy) == 4:
56         topo, arch, nic, drv = phy
57         drv = "" if drv == "dpdk" else drv.replace("_", "-")
58     else:
59         return l_data
60
61     df = pd.DataFrame(data.loc[(
62         (data["passed"] == True) &
63         (data["dut_type"] == selected["dut"]) &
64         (data["dut_version"] == selected["dutver"]) &
65         (data["release"] == selected["rls"])
66     )])
67     df = df[
68         (df.job.str.endswith(f"{topo}-{arch}")) &
69         (df.test_id.str.contains(
70             f"^.*\.{selected['area']}\..*{nic}.*{drv}.*$",
71             regex=True
72         ))
73     ]
74     if drv == "dpdk":
75         for driver in C.DRIVERS:
76             df.drop(
77                 df[df.test_id.str.contains(f"-{driver}-")].index,
78                 inplace=True
79             )
80
81     ttype = df["test_type"].to_list()[0]
82
83     # Prepare the coverage data
84     def _latency(hdrh_string: str, percentile: float) -> int:
85         """Get latency from HDRH string for given percentile.
86
87         :param hdrh_string: Encoded HDRH string.
88         :param percentile: Given percentile.
89         :type hdrh_string: str
90         :type percentile: float
91         :returns: The latency value for the given percentile from the encoded
92             HDRH string.
93         :rtype: int
94         """
95         try:
96             hdr_lat = hdrh.histogram.HdrHistogram.decode(hdrh_string)
97             return hdr_lat.get_value_at_percentile(percentile)
98         except (hdrh.codec.HdrLengthException, TypeError):
99             return None
100
101     def _get_suite(test_id: str) -> str:
102         """Get the suite name from the test ID.
103         """
104         return test_id.split(".")[-2].replace("2n1l-", "").\
105             replace("1n1l-", "").replace("2n-", "").replace("-ndrpdr", "")
106
107     def _get_test(test_id: str) -> str:
108         """Get the test name from the test ID.
109         """
110         return test_id.split(".")[-1].replace("-ndrpdr", "")
111
112     cov = pd.DataFrame()
113     cov["Suite"] = df.apply(lambda row: _get_suite(row["test_id"]), axis=1)
114     cov["Test Name"] = df.apply(lambda row: _get_test(row["test_id"]), axis=1)
115
116     if ttype == "device":
117         cov = cov.assign(Result="PASS")
118     else:
119         cov["Throughput_Unit"] = df["result_pdr_lower_rate_unit"]
120         cov["Throughput_NDR"] = df.apply(
121             lambda row: row["result_ndr_lower_rate_value"] / 1e6, axis=1
122         )
123         cov["Throughput_NDR_Mbps"] = df.apply(
124             lambda row: row["result_ndr_lower_bandwidth_value"] /1e9, axis=1
125         )
126         cov["Throughput_PDR"] = df.apply(
127             lambda row: row["result_pdr_lower_rate_value"] / 1e6, axis=1
128         )
129         cov["Throughput_PDR_Mbps"] = df.apply(
130             lambda row: row["result_pdr_lower_bandwidth_value"] /1e9, axis=1
131         )
132         if show_latency:
133             for way in ("Forward", "Reverse"):
134                 for pdr in (10, 50, 90):
135                     for perc in (50, 90, 99):
136                         latency = f"result_latency_{way.lower()}_pdr_{pdr}_hdrh"
137                         cov[f"Latency {way} [us]_{pdr}% PDR_P{perc}"] = \
138                             df.apply(
139                                 lambda row: _latency(row[latency], perc),
140                                 axis=1
141                             )
142
143     if csv:
144         return cov
145
146     # Split data into tables depending on the test suite.
147     for suite in cov["Suite"].unique().tolist():
148         df_suite = pd.DataFrame(cov.loc[(cov["Suite"] == suite)])
149
150         if ttype !="device":
151             unit = df_suite["Throughput_Unit"].tolist()[0]
152             df_suite.rename(
153                 columns={
154                     "Throughput_NDR": f"Throughput_NDR_M{unit}",
155                     "Throughput_PDR": f"Throughput_PDR_M{unit}"
156                 },
157                 inplace=True
158             )
159             df_suite.drop(["Suite", "Throughput_Unit"], axis=1, inplace=True)
160
161         l_data.append((suite, df_suite, ))
162
163     return l_data
164
165
166 def coverage_tables(
167         data: pd.DataFrame,
168         selected: dict,
169         show_latency: bool=True
170     ) -> list:
171     """Generate an accordion with coverage tables.
172
173     :param data: Coverage data.
174     :param selected: Dictionary with user selection.
175     :param show_latency: If True, latency is displayed in the tables.
176     :type data: pandas.DataFrame
177     :type selected: dict
178     :type show_latency: bool
179     :returns: Accordion with suite names (titles) and tables.
180     :rtype: dash_bootstrap_components.Accordion
181     """
182
183     accordion_items = list()
184     sel_data = select_coverage_data(data, selected, show_latency=show_latency)
185     for suite, cov_data in sel_data:
186         if len(cov_data.columns) == 3:  # VPP Device
187             cols = [
188                 {
189                     "name": col,
190                     "id": col,
191                     "deletable": False,
192                     "selectable": False,
193                     "type": "text"
194                 } for col in cov_data.columns
195             ]
196             style_cell={"textAlign": "left"}
197             style_cell_conditional=[
198                 {
199                     "if": {"column_id": "Result"},
200                     "textAlign": "right"
201                 }
202             ]
203         else:  # Performance
204             cols = list()
205             for idx, col in enumerate(cov_data.columns):
206                 if idx == 0:
207                     cols.append({
208                         "name": ["", "", col],
209                         "id": col,
210                         "deletable": False,
211                         "selectable": False,
212                         "type": "text"
213                     })
214                 elif idx < 5:
215                     cols.append({
216                         "name": col.split("_"),
217                         "id": col,
218                         "deletable": False,
219                         "selectable": False,
220                         "type": "numeric",
221                         "format": Format(precision=2, scheme=Scheme.fixed)
222                     })
223                 else:
224                     cols.append({
225                         "name": col.split("_"),
226                         "id": col,
227                         "deletable": False,
228                         "selectable": False,
229                         "type": "numeric",
230                         "format": Format(precision=0, scheme=Scheme.fixed)
231                     })
232             style_cell={"textAlign": "right"}
233             style_cell_conditional=[
234                 {
235                     "if": {"column_id": "Test Name"},
236                     "textAlign": "left"
237                 }
238             ]
239
240         accordion_items.append(
241             dbc.AccordionItem(
242                 title=suite,
243                 children=dash_table.DataTable(
244                     columns=cols,
245                     data=cov_data.to_dict("records"),
246                     merge_duplicate_headers=True,
247                     editable=False,
248                     filter_action="none",
249                     sort_action="native",
250                     sort_mode="multi",
251                     selected_columns=[],
252                     selected_rows=[],
253                     page_action="none",
254                     style_cell=style_cell,
255                     style_cell_conditional=style_cell_conditional
256                 )
257             )
258         )
259     return dbc.Accordion(
260         children=accordion_items,
261         class_name="gy-1 p-0",
262         start_collapsed=True,
263         always_open=True
264     )