b5de27ebe5edd3d532f5c09871c0beae5f5a43d3
[csit.git] / resources / libraries / python / model / ExportResult.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 """Module with keywords that publish parts of result structure."""
15
16 from robot.libraries.BuiltIn import BuiltIn
17
18 from resources.libraries.python.model.util import descend, get_export_data
19
20
21 def export_dut_type_and_version(dut_type=u"unknown", dut_version=u"unknown"):
22     """Export the arguments as dut type and version.
23
24     Robot tends to convert "none" into None, hence the unusual default values.
25
26     If either argument is missing, the value from robot variable is used.
27     If argument is present, the value is also stored to robot suite variable.
28
29     :param dut_type: DUT type, e.g. VPP or DPDK.
30     :param dut_version: DUT version as determined by the caller.
31     :type dut_type: Optional[str]
32     :type dut_version: Optiona[str]
33     :raises RuntimeError: If value is neither in argument not robot variable.
34     """
35     if dut_type == u"unknown":
36         dut_type = BuiltIn().get_variable_value(u"\\${DUT_TYPE}", u"unknown")
37         if dut_type == u"unknown":
38             raise RuntimeError(u"Dut type not provided.")
39     else:
40         # We want to set a variable in higher level suite setup
41         # to be available to test setup several levels lower.
42         BuiltIn().set_suite_variable(
43             u"\\${DUT_TYPE}", dut_type, u"children=True"
44         )
45     if dut_version == u"unknown":
46         dut_version = BuiltIn().get_variable_value(
47             u"\\${DUT_VERSION}", u"unknown"
48         )
49         if dut_type == u"unknown":
50             raise RuntimeError(u"Dut version not provided.")
51     else:
52         BuiltIn().set_suite_variable(
53             u"\\${DUT_VERSION}", dut_version, u"children=True"
54         )
55     data = get_export_data()
56     data[u"dut_type"] = dut_type.lower()
57     data[u"dut_version"] = dut_version
58
59
60 def export_tg_type_and_version(tg_type=u"unknown", tg_version=u"unknown"):
61     """Export the arguments as tg type and version.
62
63     Robot tends to convert "none" into None, hence the unusual default values.
64
65     If either argument is missing, the value from robot variable is used.
66     If argument is present, the value is also stored to robot suite variable.
67
68     :param tg_type: TG type, e.g. TREX.
69     :param tg_version: TG version as determined by the caller.
70     :type tg_type: Optional[str]
71     :type tg_version: Optiona[str]
72     :raises RuntimeError: If value is neither in argument not robot variable.
73     """
74     if tg_type == u"unknown":
75         tg_type = BuiltIn().get_variable_value(u"\\${TG_TYPE}", u"unknown")
76         if tg_type == u"unknown":
77             raise RuntimeError(u"TG type not provided.")
78     else:
79         # We want to set a variable in higher level suite setup
80         # to be available to test setup several levels lower.
81         BuiltIn().set_suite_variable(
82             u"\\${TG_TYPE}", tg_type, u"children=True"
83         )
84     if tg_version == u"unknown":
85         tg_version = BuiltIn().get_variable_value(
86             u"\\${TG_VERSION}", u"unknown"
87         )
88         if tg_type == u"unknown":
89             raise RuntimeError(u"TG version not provided.")
90     else:
91         BuiltIn().set_suite_variable(
92             u"\\${TG_VERSION}", tg_version, u"children=True"
93         )
94     data = get_export_data()
95     data[u"tg_type"] = tg_type.lower()
96     data[u"tg_version"] = tg_version
97
98
99 def append_mrr_value(mrr_value, unit):
100     """Store mrr value to proper place so it is dumped into json.
101
102     The value is appended only when unit is not empty.
103
104     :param mrr_value: Forwarding rate from MRR trial.
105     :param unit: Unit of measurement for the rate.
106     :type mrr_value: float
107     :type unit: str
108     """
109     if not unit:
110         return
111     data = get_export_data()
112     data[u"result"][u"type"] = u"mrr"
113     rate_node = descend(descend(data[u"result"], u"receive_rate"), "rate")
114     rate_node[u"unit"] = str(unit)
115     values_list = descend(rate_node, u"values", list)
116     values_list.append(float(mrr_value))
117
118
119 def export_search_bound(text, value, unit, bandwidth=None):
120     """Store bound value and unit.
121
122     This function works for both NDRPDR and SOAK, decided by text.
123
124     If a node does not exist, it is created.
125     If a previous value exists, it is overwritten silently.
126     Result type is set (overwritten) to ndrpdr (or soak).
127
128     Text is used to determine whether it is ndr or pdr, upper or lower bound,
129     as the Robot caller has the information only there.
130
131     :param text: Info from Robot caller to determime bound type.
132     :param value: The bound value in packets (or connections) per second.
133     :param unit: Rate unit the bound is measured (or estimated) in.
134     :param bandwidth: The same value recomputed into L1 bits per second.
135     :type text: str
136     :type value: float
137     :type unit: str
138     :type bandwidth: Optional[float]
139     """
140     value = float(value)
141     text = str(text).lower()
142     result_type = u"soak" if u"plrsearch" in text else u"ndrpdr"
143     upper_or_lower = u"upper" if u"upper" in text else u"lower"
144     ndr_or_pdr = u"ndr" if u"ndr" in text else u"pdr"
145
146     result_node = get_export_data()[u"result"]
147     result_node[u"type"] = result_type
148     rate_item = dict(rate=dict(value=value, unit=unit))
149     if bandwidth:
150         rate_item[u"bandwidth"] = dict(value=float(bandwidth), unit=u"bps")
151     if result_type == u"soak":
152         descend(result_node, u"critical_rate")[upper_or_lower] = rate_item
153         return
154     descend(result_node, ndr_or_pdr)[upper_or_lower] = rate_item
155
156
157 def _add_latency(result_node, percent, whichward, latency_string):
158     """Descend to a corresponding node and add values from latency string.
159
160     This is an internal block, moved out from export_ndrpdr_latency,
161     as it can be called up to 4 times.
162
163     :param result_node: UTI tree node to descend from.
164     :param percent: Percent value to use in node key (90, 50, 10, 0).
165     :param whichward: "forward" or "reverse".
166     :param latency_item: Unidir output from TRex utility, min/avg/max/hdrh.
167     :type result_node: dict
168     :type percent: int
169     :type whichward: str
170     :latency_string: str
171     """
172     l_min, l_avg, l_max, l_hdrh = latency_string.split(u"/", 3)
173     whichward_node = descend(result_node, f"latency_{whichward}")
174     percent_node = descend(whichward_node, f"pdr_{percent}")
175     percent_node[u"min"] = int(l_min)
176     percent_node[u"avg"] = int(l_avg)
177     percent_node[u"max"] = int(l_max)
178     percent_node[u"hdrh"] = l_hdrh
179     percent_node[u"unit"] = u"us"
180
181
182 def export_ndrpdr_latency(text, latency):
183     """Store NDRPDR hdrh latency data.
184
185     If "latency" node does not exist, it is created.
186     If a previous value exists, it is overwritten silently.
187
188     Text is used to determine what percentage of PDR is the load,
189     as the Robot caller has the information only there.
190
191     Reverse data may be missing, we assume the test was unidirectional.
192
193     :param text: Info from Robot caller to determime load.
194     :param latency: Output from TRex utility, min/avg/max/hdrh.
195     :type text: str
196     :type latency: 1-tuple or 2-tuple of str
197     """
198     result_node = get_export_data()[u"result"]
199     percent = 0
200     if u"90" in text:
201         percent = 90
202     elif u"50" in text:
203         percent = 50
204     elif u"10" in text:
205         percent = 10
206     _add_latency(result_node, percent, u"forward", latency[0])
207     # Else TRex does not support latency measurement for this traffic profile.
208     if len(latency) < 2:
209         return
210     _add_latency(result_node, percent, u"reverse", latency[1])
211
212
213 def export_reconf_result(packet_rate, packet_loss, bandwidth):
214     """Export the results from a reconf test.
215
216     :param packet_rate: Aggregate offered load in packets per second.
217     :param packet_loss: How many of the packets were dropped or unsent.
218     :param bandwidth: The offered load recomputed into L1 bits per second.
219     :type packet_rate: float
220     :type packet_loss: int
221     :type bandwidth: float
222     """
223     result_node = get_export_data()["result"]
224     result_node["type"] = "reconf"
225
226     time_loss = int(packet_loss) / float(packet_rate)
227     result_node["aggregate_rate"] = dict(
228         bandwidth=dict(
229             unit="bps",
230             value=float(bandwidth)
231         ),
232         rate=dict(
233             unit="pps",
234             value=float(packet_rate)
235         )
236     )
237     result_node["loss"] = dict(
238         packet=dict(
239             unit="packets",
240             value=int(packet_loss)
241         ),
242         time=dict(
243             unit="s",
244             value=time_loss
245         )
246     )
247
248
249 def append_telemetry(telemetry_item):
250     """Append telemetry entry to proper place so it is dumped into json.
251
252     :param telemetry_item: Telemetry entry.
253     :type telemetry_item: str
254     """
255     data = get_export_data()
256     data[u"telemetry"].append(telemetry_item)