VAT-to-PAPI: VPPCounters
[csit.git] / resources / libraries / python / VppCounters.py
1 # Copyright (c) 2019 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 """VPP counters utilities library."""
15
16 import time
17
18 from pprint import pformat
19
20 from robot.api import logger
21 from resources.libraries.python.PapiExecutor import PapiExecutor
22 from resources.libraries.python.topology import NodeType, Topology
23
24
25 class VppCounters(object):
26     """VPP counters utilities."""
27
28     def __init__(self):
29         self._stats_table = None
30
31     @staticmethod
32     def _run_cli_cmd(node, cmd, log=True):
33         """Run a CLI command.
34
35         :param node: Node to run command on.
36         :param cmd: The CLI command to be run on the node.
37         :param log: If True, the response is logged.
38         :type node: dict
39         :type cmd: str
40         :type log: bool
41         :returns: Verified data from PAPI response.
42         :rtype: dict
43         """
44         cli = 'cli_inband'
45         args = dict(cmd=cmd)
46         err_msg = "Failed to run 'cli_inband {cmd}' PAPI command on host " \
47                   "{host}".format(host=node['host'], cmd=cmd)
48
49         with PapiExecutor(node) as papi_exec:
50             data = papi_exec.add(cli, **args).get_replies(err_msg). \
51                 verify_reply(err_msg=err_msg)
52
53         if log:
54             logger.info("{cmd}:\n{data}".format(cmd=cmd, data=data["reply"]))
55
56         return data
57
58     @staticmethod
59     def _get_non_zero_items(data):
60         """Extract and return non-zero items from the input data.
61
62         :param data: Data to filter.
63         :type data: dict
64         :returns: Dictionary with non-zero items.
65         :rtype dict
66         """
67         return {k: data[k] for k in data.keys() if sum(data[k])}
68
69     @staticmethod
70     def vpp_show_errors(node):
71         """Run "show errors" debug CLI command.
72
73         :param node: Node to run command on.
74         :type node: dict
75         """
76         VppCounters._run_cli_cmd(node, 'show errors')
77
78     @staticmethod
79     def vpp_show_errors_verbose(node):
80         """Run "show errors verbose" debug CLI command.
81
82         :param node: Node to run command on.
83         :type node: dict
84         """
85         VppCounters._run_cli_cmd(node, 'show errors verbose')
86
87     @staticmethod
88     def vpp_show_errors_on_all_duts(nodes, verbose=False):
89         """Show errors on all DUTs.
90
91         :param nodes: VPP nodes.
92         :param verbose: If True show verbose output.
93         :type nodes: dict
94         :type verbose: bool
95         """
96         for node in nodes.values():
97             if node['type'] == NodeType.DUT:
98                 if verbose:
99                     VppCounters.vpp_show_errors_verbose(node)
100                 else:
101                     VppCounters.vpp_show_errors(node)
102
103     @staticmethod
104     def vpp_show_runtime(node, log_zeros=False):
105         """Run "show runtime" CLI command.
106
107         :param node: Node to run command on.
108         :param log_zeros: Log also items with zero values.
109         :type node: dict
110         :type log_zeros: bool
111         """
112         args = dict(path='^/sys/node')
113         with PapiExecutor(node) as papi_exec:
114             stats = papi_exec.add("vpp-stats", **args).get_stats()[0]
115
116         names = stats['/sys/node/names']
117
118         if not names:
119             return
120
121         runtime = []
122         runtime_non_zero = []
123
124         for name in names:
125             runtime.append({'name': name})
126
127         for idx, runtime_item in enumerate(runtime):
128
129             calls_th = []
130             for thread in stats['/sys/node/calls']:
131                 calls_th.append(thread[idx])
132             runtime_item["calls"] = calls_th
133
134             vectors_th = []
135             for thread in stats['/sys/node/vectors']:
136                 vectors_th.append(thread[idx])
137             runtime_item["vectors"] = vectors_th
138
139             suspends_th = []
140             for thread in stats['/sys/node/suspends']:
141                 suspends_th.append(thread[idx])
142             runtime_item["suspends"] = suspends_th
143
144             clocks_th = []
145             for thread in stats['/sys/node/clocks']:
146                 clocks_th.append(thread[idx])
147             runtime_item["clocks"] = clocks_th
148
149             if (sum(calls_th) or sum(vectors_th) or
150                     sum(suspends_th) or sum(clocks_th)):
151                 runtime_non_zero.append(runtime_item)
152
153         if log_zeros:
154             logger.info("Runtime:\n{runtime}".format(
155                 runtime=pformat(runtime)))
156         else:
157             logger.info("Runtime:\n{runtime}".format(
158                 runtime=pformat(runtime_non_zero)))
159
160     @staticmethod
161     def vpp_show_runtime_verbose(node):
162         """Run "show runtime verbose" CLI command.
163
164         TODO: Remove?
165               Only verbose output is possible to get using VPPStats.
166
167         :param node: Node to run command on.
168         :type node: dict
169         """
170         VppCounters.vpp_show_runtime(node)
171
172     @staticmethod
173     def show_runtime_counters_on_all_duts(nodes):
174         """Clear VPP runtime counters on all DUTs.
175
176         :param nodes: VPP nodes.
177         :type nodes: dict
178         """
179         for node in nodes.values():
180             if node['type'] == NodeType.DUT:
181                 VppCounters.vpp_show_runtime(node)
182
183     @staticmethod
184     def vpp_show_hardware_detail(node):
185         """Run "show hardware-interfaces detail" debug CLI command.
186
187         :param node: Node to run command on.
188         :type node: dict
189         """
190         VppCounters._run_cli_cmd(node, 'show hardware detail')
191
192     @staticmethod
193     def vpp_clear_runtime(node):
194         """Run "clear runtime" CLI command.
195
196         :param node: Node to run command on.
197         :type node: dict
198         :returns: Verified data from PAPI response.
199         :rtype: dict
200         """
201         return VppCounters._run_cli_cmd(node, 'clear runtime', log=False)
202
203     @staticmethod
204     def clear_runtime_counters_on_all_duts(nodes):
205         """Run "clear runtime" CLI command on all DUTs.
206
207         :param nodes: VPP nodes.
208         :type nodes: dict
209         """
210         for node in nodes.values():
211             if node['type'] == NodeType.DUT:
212                 VppCounters.vpp_clear_runtime(node)
213
214     @staticmethod
215     def vpp_clear_interface_counters(node):
216         """Run "clear interfaces" CLI command.
217
218         :param node: Node to run command on.
219         :type node: dict
220         :returns: Verified data from PAPI response.
221         :rtype: dict
222         """
223         return VppCounters._run_cli_cmd(node, 'clear interfaces', log=False)
224
225     @staticmethod
226     def clear_interface_counters_on_all_duts(nodes):
227         """Clear interface counters on all DUTs.
228
229         :param nodes: VPP nodes.
230         :type nodes: dict
231         """
232         for node in nodes.values():
233             if node['type'] == NodeType.DUT:
234                 VppCounters.vpp_clear_interface_counters(node)
235
236     @staticmethod
237     def vpp_clear_hardware_counters(node):
238         """Run "clear hardware" CLI command.
239
240         :param node: Node to run command on.
241         :type node: dict
242         :returns: Verified data from PAPI response.
243         :rtype: dict
244         """
245         return VppCounters._run_cli_cmd(node, 'clear hardware', log=False)
246
247     @staticmethod
248     def clear_hardware_counters_on_all_duts(nodes):
249         """Clear hardware counters on all DUTs.
250
251         :param nodes: VPP nodes.
252         :type nodes: dict
253         """
254         for node in nodes.values():
255             if node['type'] == NodeType.DUT:
256                 VppCounters.vpp_clear_hardware_counters(node)
257
258     @staticmethod
259     def vpp_clear_errors_counters(node):
260         """Run "clear errors" CLI command.
261
262         :param node: Node to run command on.
263         :type node: dict
264         :returns: Verified data from PAPI response.
265         :rtype: dict
266         """
267         return VppCounters._run_cli_cmd(node, 'clear errors', log=False)
268
269     @staticmethod
270     def clear_error_counters_on_all_duts(nodes):
271         """Clear VPP errors counters on all DUTs.
272
273         :param nodes: VPP nodes.
274         :type nodes: dict
275         """
276         for node in nodes.values():
277             if node['type'] == NodeType.DUT:
278                 VppCounters.vpp_clear_errors_counters(node)
279
280     def vpp_get_ipv4_interface_counter(self, node, interface):
281         """
282
283         :param node: Node to get interface IPv4 counter on.
284         :param interface: Interface name.
285         :type node: dict
286         :type interface: str
287         :returns: Interface IPv4 counter.
288         :rtype: int
289         """
290         return self.vpp_get_ipv46_interface_counter(node, interface, False)
291
292     def vpp_get_ipv6_interface_counter(self, node, interface):
293         """
294
295         :param node: Node to get interface IPv6 counter on.
296         :param interface: Interface name.
297         :type node: dict
298         :type interface: str
299         :returns: Interface IPv6 counter.
300         :rtype: int
301         """
302         return self.vpp_get_ipv46_interface_counter(node, interface, True)
303
304     def vpp_get_ipv46_interface_counter(self, node, interface, is_ipv6=True):
305         """Return interface IPv4/IPv6 counter.
306
307         :param node: Node to get interface IPv4/IPv6 counter on.
308         :param interface: Interface name.
309         :param is_ipv6: Specify IP version.
310         :type node: dict
311         :type interface: str
312         :type is_ipv6: bool
313         :returns: Interface IPv4/IPv6 counter.
314         :rtype: int
315         """
316         version = 'ip6' if is_ipv6 else 'ip4'
317         topo = Topology()
318         if_index = topo.get_interface_sw_index(node, interface)
319         if if_index is None:
320             logger.trace('{i} sw_index not found.'.format(i=interface))
321             return 0
322
323         if_counters = self._stats_table.get('interface_counters')
324         if not if_counters:
325             logger.trace('No interface counters.')
326             return 0
327         for counter in if_counters:
328             if counter['vnet_counter_type'] == version:
329                 data = counter['data']
330                 return data[if_index]
331         logger.trace('{i} {v} counter not found.'.format(
332             i=interface, v=version))
333         return 0
334
335     @staticmethod
336     def show_vpp_statistics(node):
337         """Show [error, hardware, interface] stats.
338
339         :param node: VPP node.
340         :type node: dict
341         """
342         VppCounters.vpp_show_errors(node)
343         VppCounters.vpp_show_hardware_detail(node)
344         VppCounters.vpp_show_runtime(node)
345
346     @staticmethod
347     def show_statistics_on_all_duts(nodes, sleeptime=5):
348         """Show VPP statistics on all DUTs.
349
350         :param nodes: VPP nodes.
351         :type nodes: dict
352         :param sleeptime: Time to wait for traffic to arrive back to TG.
353         :type sleeptime: int
354         """
355         logger.trace('Waiting for statistics to be collected')
356         time.sleep(sleeptime)
357         for node in nodes.values():
358             if node['type'] == NodeType.DUT:
359                 VppCounters.show_vpp_statistics(node)