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