feat(model): Cleanup and introduce telemetry
[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     data = get_export_data()
147     result_node = data[u"result"]
148     result_node[u"type"] = result_type
149     rate_item = dict(rate=dict(value=value, unit=unit))
150     if bandwidth:
151         rate_item[u"bandwidth"] = dict(value=float(bandwidth), unit=u"bps")
152     if result_type == u"soak":
153         descend(result_node, u"critical_rate")[upper_or_lower] = rate_item
154         return
155     descend(result_node, ndr_or_pdr)[upper_or_lower] = rate_item
156
157
158 def _add_latency(result_node, percent, whichward, latency_string):
159     """Descend to a corresponding node and add values from latency string.
160
161     This is an internal block, moved out from export_ndrpdr_latency,
162     as it can be called up to 4 times.
163
164     :param result_node: UTI tree node to descend from.
165     :param percent: Percent value to use in node key (90, 50, 10, 0).
166     :param whichward: "forward" or "reverse".
167     :param latency_item: Unidir output from TRex utility, min/avg/max/hdrh.
168     :type result_node: dict
169     :type percent: int
170     :type whichward: str
171     :latency_string: str
172     """
173     l_min, l_avg, l_max, l_hdrh = latency_string.split(u"/", 3)
174     whichward_node = descend(result_node, f"latency_{whichward}")
175     percent_node = descend(whichward_node, f"pdr_{percent}")
176     percent_node[u"min"] = int(l_min)
177     percent_node[u"avg"] = int(l_avg)
178     percent_node[u"max"] = int(l_max)
179     percent_node[u"hdrh"] = l_hdrh
180     percent_node[u"unit"] = u"us"
181
182
183 def export_ndrpdr_latency(text, latency):
184     """Store NDRPDR hdrh latency data.
185
186     If "latency" node does not exist, it is created.
187     If a previous value exists, it is overwritten silently.
188
189     Text is used to determine what percentage of PDR is the load,
190     as the Robot caller has the information only there.
191
192     Reverse data may be missing, we assume the test was unidirectional.
193
194     :param text: Info from Robot caller to determime load.
195     :param latency: Output from TRex utility, min/avg/max/hdrh.
196     :type text: str
197     :type latency: 1-tuple or 2-tuple of str
198     """
199     data = get_export_data()
200     result_node = data[u"result"]
201     percent = 0
202     if u"90" in text:
203         percent = 90
204     elif u"50" in text:
205         percent = 50
206     elif u"10" in text:
207         percent = 10
208     _add_latency(result_node, percent, u"forward", latency[0])
209     # Else TRex does not support latency measurement for this traffic profile.
210     if len(latency) < 2:
211         return
212     _add_latency(result_node, percent, u"reverse", latency[1])