fix(uti): Fixing broken code part I
[csit.git] / resources / tools / telemetry / bundle_vpp.py
1 # Copyright (c) 2021 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 execution bundle."""
15
16 from copy import deepcopy
17 from logging import getLogger
18 from re import fullmatch, sub
19 import struct
20 import sys
21
22 from vpp_papi.vpp_papi import VPPApiClient as vpp_class
23
24
25 M_RUN_THREAD = (
26     r"Thread\s"
27     r"(?P<thread_id>\d+)\s"
28     r"(?P<thread_name>\S+)\s.*"
29     r"(?P<thread_lcore>\d+).*"
30 )
31 M_RUN_SEPARATOR = (
32     r"(-)+"
33 )
34 M_RUN_NODES = (
35     r"(?P<name>\S+)\s+"
36     r"(?P<state>\S+\s\S+|\S+)\s+"
37     r"(?P<calls>\d+)\s+"
38     r"(?P<vectors>\d+)\s+"
39     r"(?P<suspends>\d+)\s+"
40     r"(?P<clocks>\S+)\s+"
41     r"(?P<vectors_calls>\S+)"
42 )
43 M_RUN_TIME = (
44     r"Time\s\S+,\s\d+\ssec\sinternal\snode\svector\srate\s"
45     r"(?P<rate>\S+)\sloops/sec\s"
46     r"(?P<loops>\S+)"
47 )
48 M_INT_BEGIN = (
49     r"(?P<name>\S+)\s+"
50     r"(?P<index>\S+)\s+"
51     r"(?P<state>\S+)\s+"
52     r"(?P<mtu>\S+)\s+"
53     r"(?P<counter>\S+\s\S+|\S+)\s+"
54     r"(?P<count>\d+)"
55 )
56 M_INT_CONT = (
57     r"\s+"
58     r"(?P<counter>\S+\s\S+|\S+)\s+"
59     r"(?P<count>\d+)"
60 )
61 M_NODE_COUNTERS_THREAD = (
62     r"Thread\s"
63     r"(?P<thread_id>\d+)\s\("
64     r"(?P<thread_name>\S+)\):\s*"
65 )
66 M_NODE_COUNTERS = (
67     r"\s*"
68     r"(?P<count>\d+)\s+"
69     r"(?P<name>\S+)\s+"
70     r"(?P<reason>(\S+\s)+)\s+"
71     r"(?P<severity>\S+)\s+"
72     r"(?P<index>\d+)\s*"
73 )
74 M_PMB_CS_HEADER = (
75     r"\s*per-thread\s+context\s+switches.*"
76 )
77 M_PMB_CS = (
78     r"(?P<thread_name>\S+)\s+\("
79     r"(?P<thread_id>\S+)\)\s+\S+\s+"
80     r"(?P<context_switches>[\d\.]+)"
81 )
82 M_PMB_PF_HEADER = (
83     r"\s*per-thread\s+page\s+faults.*"
84 )
85 M_PMB_PF = (
86     r"(?P<thread_name>\S+)\s+\("
87     r"(?P<thread_id>\S+)\)\s+\S+\s+"
88     r"(?P<minor_page_faults>[\d\.]+)\s+"
89     r"(?P<major_page_faults>[\d\.]+)"
90 )
91 M_PMB_THREAD = (
92     r"\s*"
93     r"(?P<thread_name>\S+)\s+\("
94     r"(?P<thread_id>\d+)\)\s*"
95 )
96 M_PMB_IC_HEADER = (
97     r"\s*instructions/packet,\s+cycles/packet\s+and\s+IPC.*"
98 )
99 M_PMB_IC_NODE = (
100     r"\s*"
101     r"(?P<node_name>\S+)\s+"
102     r"(?P<calls>[\d\.]+)\s+"
103     r"(?P<packets>[\d\.]+)\s+"
104     r"(?P<packets_per_call>[\d\.]+)\s+"
105     r"(?P<clocks_per_packets>[\d\.]+)\s+"
106     r"(?P<instructions_per_packets>[\d\.]+)\s+"
107     r"(?P<ipc>[\d\.]+)"
108 )
109 M_PMB_CM_HEADER = (
110     r"\s*cache\s+hits\s+and\s+misses.*"
111 )
112 M_PMB_CM_NODE = (
113     r"\s*"
114     r"(?P<node_name>\S+)\s+"
115     r"(?P<l1_hit>[\d\.]+)\s+"
116     r"(?P<l1_miss>[\d\.]+)\s+"
117     r"(?P<l2_hit>[\d\.]+)\s+"
118     r"(?P<l2_miss>[\d\.]+)\s+"
119     r"(?P<l3_hit>[\d\.]+)\s+"
120     r"(?P<l3_miss>[\d\.]+)"
121 )
122 M_PMB_LO_HEADER = (
123     r"\s*load\s+operations.*"
124 )
125 M_PMB_LO_NODE = (
126     r"\s*"
127     r"(?P<node_name>\S+)\s+"
128     r"(?P<calls>[\d\.]+)\s+"
129     r"(?P<packets>[\d\.]+)\s+"
130     r"(?P<one>[\d\.]+)\s+"
131     r"(?P<two>[\d\.]+)\s+"
132     r"(?P<three>[\d\.]+)"
133 )
134 M_PMB_BM_HEADER = (
135     r"\s*Branches,\s+branches\s+taken\s+and\s+mis-predictions.*"
136 )
137 M_PMB_BM_NODE = (
138     r"\s*"
139     r"(?P<node_name>\S+)\s+"
140     r"(?P<branches_per_call>[\d\.]+)\s+"
141     r"(?P<branches_per_packet>[\d\.]+)\s+"
142     r"(?P<taken_per_call>[\d\.]+)\s+"
143     r"(?P<taken_per_packet>[\d\.]+)\s+"
144     r"(?P<mis_predictions>[\d\.]+)"
145 )
146 M_PMB_PL_HEADER = (
147     r"\s*Thread\s+power\s+licensing.*"
148 )
149 M_PMB_PL_NODE = (
150     r"\s*"
151     r"(?P<node_name>\S+)\s+"
152     r"(?P<lvl0>[\d\.]+)\s+"
153     r"(?P<lvl1>[\d\.]+)\s+"
154     r"(?P<lvl2>[\d\.]+)\s+"
155     r"(?P<throttle>[\d\.]+)"
156 )
157 M_PMB_MB_HEADER = (
158     r"\s*memory\s+reads\s+and\s+writes\s+per\s+memory\s+controller.*"
159 )
160 M_PMB_MB = (
161     r"\s*"
162     r"(?P<name>\S+)\s+"
163     r"(?P<runtime>[\d\.]+)\s+"
164     r"(?P<reads_mbs>[\d\.]+)\s+"
165     r"(?P<writes_mbs>[\d\.]+)\s+"
166     r"(?P<total_mbs>[\d\.]+)"
167 )
168
169
170 class BundleVpp:
171     """
172     Creates a VPP object. This is the main object for defining a VPP program,
173     and interacting with its output.
174     """
175     def __init__(self, program, serializer, hook):
176         """
177         Initialize Bundle VPP class.
178
179         :param program: VPP instructions.
180         :param serializer: Metric serializer.
181         :param hook: VPP API socket.
182         :type program: dict
183         :type serializer: Serializer
184         :type hook: int
185         """
186         self.obj = None
187         self.code = program[u"code"]
188         self.metrics = program[u"metrics"]
189         self.api_command_list = list()
190         self.api_replies_list = list()
191         self.serializer = serializer
192
193         vpp_class.apidir = u"/usr/share/vpp/api"
194         self.obj = vpp_class(
195             use_socket=True,
196             server_address=hook,
197             async_thread=False,
198             read_timeout=14,
199             logger=getLogger(__name__)
200         )
201
202     def attach(self, duration):
203         """
204         Attach events to VPP.
205
206         :param duration: Trial duration.
207         :type duration: int
208         """
209         try:
210             self.obj.connect(name=u"telemetry")
211         except (ConnectionRefusedError, OSError):
212             getLogger(__name__).error(u"Cannot connect to VPP!")
213             sys.exit(1)
214
215         for command in self.code.splitlines():
216             api_name = u"cli_inband"
217             api_args = dict(cmd=command.format(duration=duration))
218             self.api_command_list.append(
219                 dict(api_name=api_name, api_args=deepcopy(api_args))
220             )
221
222     def detach(self):
223         """
224         Detach from VPP.
225         """
226         self.obj.disconnect()
227
228     def fetch_data(self):
229         """
230         Fetch data by invoking API calls to VPP socket.
231         """
232         for command in self.api_command_list:
233             try:
234                 papi_fn = getattr(self.obj.api, command[u"api_name"])
235                 getLogger(__name__).info(command[u"api_args"][u"cmd"])
236                 replies = papi_fn(**command[u"api_args"])
237             except (AttributeError, IOError, struct.error) as err:
238                 raise AssertionError(err)
239
240             if not isinstance(replies, list):
241                 replies = [replies]
242             for reply in replies:
243                 self.api_replies_list.append(reply)
244                 reply = sub(r"\x1b[^m]*m", u"", reply.reply)
245                 if reply:
246                     getLogger(__name__).info(reply)
247                 else:
248                     getLogger(__name__).info(u"<no reply>")
249         self.serializer.create(metrics=self.metrics)
250
251     def process_data(self):
252         """
253         Post process command reply.
254         """
255         for command in zip(self.api_command_list, self.api_replies_list):
256             self_fn = command[0][u"api_args"][u"cmd"]
257             try:
258                 self_fn = getattr(self, self_fn.replace(u" ", u"_"))
259                 self_fn(command[1].reply)
260             except AttributeError:
261                 pass
262
263     def show_interface(self, reply):
264         """
265         Parse the show interface output.
266
267         Output format:
268         {
269             "name": "rx_packets",
270             "labels": {
271                 "name": "tap0",
272                 "index": "0",
273             },
274             "value": "31",
275         },
276
277         :param reply: API reply.
278         :type reply: str
279         """
280         for line in reply.splitlines():
281             item = dict()
282             labels = dict()
283             if fullmatch(M_INT_BEGIN, line):
284                 ifc = fullmatch(M_INT_BEGIN, line).groupdict()
285                 metric = ifc[u"counter"].replace(" ", "_").replace("-", "_")
286                 item[u"name"] = metric
287                 item[u"value"] = ifc[u"count"]
288             if fullmatch(M_INT_CONT, line):
289                 ifc_cnt = fullmatch(M_INT_CONT, line).groupdict()
290                 metric = ifc_cnt[u"counter"].replace(" ", "_").replace("-", "_")
291                 item[u"name"] = metric
292                 item[u"value"] = ifc_cnt[u"count"]
293             if fullmatch(M_INT_BEGIN, line) or fullmatch(M_INT_CONT, line):
294                 labels[u"name"] = ifc[u"name"]
295                 labels[u"index"] = ifc[u"index"]
296                 item[u"labels"] = labels
297                 self.serializer.serialize(
298                     metric=metric, labels=labels, item=item
299                 )
300
301     def show_runtime(self, reply):
302         """
303         Parse the show runtime output.
304
305         Output format:
306         {
307             "name": "clocks",
308             "labels": {
309                 "name": "virtio-input",
310                 "state": "polling",
311                 "thread_name": "vpp_wk_1",
312                 "thread_id": "2",
313                 "thread_lcore": "3",
314             },
315             "value": "3.17e2",
316         },
317
318         :param reply: API reply.
319         :type reply: str
320         """
321         for line in reply.splitlines():
322             if fullmatch(M_RUN_THREAD, line):
323                 thread = fullmatch(M_RUN_THREAD, line).groupdict()
324             if fullmatch(M_RUN_NODES, line):
325                 nodes = fullmatch(M_RUN_NODES, line).groupdict()
326                 for metric in self.serializer.metric_registry:
327                     item = dict()
328                     labels = dict()
329                     item[u"name"] = metric
330                     labels[u"name"] = nodes[u"name"]
331                     labels[u"state"] = nodes[u"state"]
332                     try:
333                         labels[u"thread_name"] = thread[u"thread_name"]
334                         labels[u"thread_id"] = thread[u"thread_id"]
335                         labels[u"thread_lcore"] = thread[u"thread_lcore"]
336                     except UnboundLocalError:
337                         labels[u"thread_name"] = u"vpp_main"
338                         labels[u"thread_id"] = u"0"
339                         labels[u"thread_lcore"] = u"0"
340                     item[u"labels"] = labels
341                     item[u"value"] = nodes[metric]
342                     self.serializer.serialize(
343                         metric=metric, labels=labels, item=item
344                     )
345
346     def show_node_counters_verbose(self, reply):
347         """
348         Parse the show node conuter output.
349
350         Output format:
351         {
352             "name": "node_counters",
353             "labels": {
354                 "name": "dpdk-input",
355                 "reason": "no_error",
356                 "severity": "error",
357                 "thread_name": "vpp_wk_1",
358                 "thread_id": "2",
359             },
360             "value": "1",
361         },
362
363         :param reply: API reply.
364         :type reply: str
365         """
366         for line in reply.splitlines():
367             if fullmatch(M_NODE_COUNTERS_THREAD, line):
368                 thread = fullmatch(M_NODE_COUNTERS_THREAD, line).groupdict()
369             if fullmatch(M_NODE_COUNTERS, line):
370                 nodes = fullmatch(M_NODE_COUNTERS, line).groupdict()
371                 for metric in self.serializer.metric_registry_registry:
372                     item = dict()
373                     labels = dict()
374                     item[u"name"] = metric
375                     labels[u"name"] = nodes[u"name"]
376                     labels[u"reason"] = nodes[u"reason"]
377                     labels[u"severity"] = nodes[u"severity"]
378                     try:
379                         labels[u"thread_name"] = thread[u"thread_name"]
380                         labels[u"thread_id"] = thread[u"thread_id"]
381                     except UnboundLocalError:
382                         labels[u"thread_name"] = u"vpp_main"
383                         labels[u"thread_id"] = u"0"
384                     item[u"labels"] = labels
385                     item[u"value"] = nodes[u"count"]
386                     self.serializer.serialize(
387                         metric=metric, labels=labels, item=item
388                     )
389
390     def show_perfmon_statistics(self, reply):
391         """
392         Parse the permon output.
393
394         Output format:
395         {
396             "name": "clocks",
397             "labels": {
398                 "name": "virtio-input",
399                 "state": "polling",
400                 "thread_name": "vpp_wk_1",
401                 "thread_id": "2",
402                 "thread_lcore": "3",
403             },
404             "value": "3.17e2",
405         },
406
407         :param reply: API reply.
408         :type reply: str
409         """
410         def perfmon_threads(reply, regex_threads):
411             for line in reply.splitlines():
412                 if fullmatch(regex_threads, line):
413                     threads = fullmatch(regex_threads, line).groupdict()
414                     for metric in self.serializer.metric_registry:
415                         item = dict()
416                         labels = dict()
417                         item[u"name"] = metric
418                         labels[u"name"] = threads[u"thread_name"]
419                         labels[u"id"] = threads[u"thread_id"]
420                         item[u"labels"] = labels
421                         item[u"value"] = threads[metric]
422                         self.serializer.serialize(
423                             metric=metric, labels=labels, item=item
424                         )
425
426         def perfmon_nodes(reply, regex_threads, regex_nodes):
427             for line in reply.splitlines():
428                 if fullmatch(regex_threads, line):
429                     thread = fullmatch(regex_threads, line).groupdict()
430                 if fullmatch(regex_nodes, line):
431                     node = fullmatch(regex_nodes, line).groupdict()
432                     for metric in self.serializer.metric_registry:
433                         item = dict()
434                         labels = dict()
435                         item[u"name"] = metric
436                         labels[u"name"] = node[u"node_name"]
437                         labels[u"thread_name"] = thread[u"thread_name"]
438                         labels[u"thread_id"] = thread[u"thread_id"]
439                         item[u"labels"] = labels
440                         item[u"value"] = node[metric]
441                         self.serializer.serialize(
442                             metric=metric, labels=labels, item=item
443                         )
444
445         def perfmon_system(reply, regex_line):
446             for line in reply.splitlines():
447                 if fullmatch(regex_line, line):
448                     name = fullmatch(regex_line, line).groupdict()
449                     for metric in self.serializer.metric_registry:
450                         item = dict()
451                         labels = dict()
452                         item[u"name"] = metric
453                         labels[u"name"] = name[u"name"]
454                         item[u"labels"] = labels
455                         item[u"value"] = name[metric]
456                         self.serializer.serialize(
457                             metric=metric, labels=labels, item=item
458                         )
459
460         reply = sub(r"\x1b[^m]*m", u"", reply)
461
462         if fullmatch(M_PMB_CS_HEADER, reply.splitlines()[0]):
463             perfmon_threads(reply, M_PMB_CS)
464         if fullmatch(M_PMB_PF_HEADER, reply.splitlines()[0]):
465             perfmon_threads(reply, M_PMB_PF)
466         if fullmatch(M_PMB_IC_HEADER, reply.splitlines()[0]):
467             perfmon_nodes(reply, M_PMB_THREAD, M_PMB_IC_NODE)
468         if fullmatch(M_PMB_CM_HEADER, reply.splitlines()[0]):
469             perfmon_nodes(reply, M_PMB_THREAD, M_PMB_CM_NODE)
470         if fullmatch(M_PMB_LO_HEADER, reply.splitlines()[0]):
471             perfmon_nodes(reply, M_PMB_THREAD, M_PMB_LO_NODE)
472         if fullmatch(M_PMB_BM_HEADER, reply.splitlines()[0]):
473             perfmon_nodes(reply, M_PMB_THREAD, M_PMB_BM_NODE)
474         if fullmatch(M_PMB_PL_HEADER, reply.splitlines()[0]):
475             perfmon_nodes(reply, M_PMB_THREAD, M_PMB_PL_NODE)
476         if fullmatch(M_PMB_MB_HEADER, reply.splitlines()[0]):
477             perfmon_system(reply, M_PMB_MB)
478
479     def show_version(self, reply):
480         """
481         Parse the version output.
482
483         Output format:
484         {
485             "name": "version",
486             "labels": {
487                 "version": "v21.06-rc0~596-g1ca6c65e5~b1065",
488             },
489             "value": 1.0,
490         },
491
492         :param reply: API reply.
493         :type reply: str
494         """
495         for metric in self.serializer.metric_registry:
496             version = reply.split()[1]
497             item = dict()
498             labels = dict()
499             item[u"name"] = metric
500             labels[u"version"] = version
501             item[u"labels"] = labels
502             item[u"value"] = 1.0
503             self.serializer.serialize(
504                 metric=metric, labels=labels, item=item
505             )