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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """VPP execution bundle."""
16 from copy import deepcopy
17 from logging import getLogger
18 from re import fullmatch, sub
22 from vpp_papi.vpp_papi import VPPApiClient as vpp_class
27 r"(?P<thread_id>\d+)\s"
28 r"(?P<thread_name>\S+)\s.*"
29 r"(?P<thread_lcore>\d+).*"
36 r"(?P<state>\S+\s\S+|\S+)\s+"
38 r"(?P<vectors>\d+)\s+"
39 r"(?P<suspends>\d+)\s+"
41 r"(?P<vectors_calls>\S+)"
44 r"Time\s\S+,\s\d+\ssec\sinternal\snode\svector\srate\s"
45 r"(?P<rate>\S+)\sloops/sec\s"
53 r"(?P<counter>\S+\s\S+|\S+)\s+"
58 r"(?P<counter>\S+\s\S+|\S+)\s+"
61 M_NODE_COUNTERS_THREAD = (
63 r"(?P<thread_id>\d+)\s\("
64 r"(?P<thread_name>\S+)\):\s*"
70 r"(?P<reason>(\S+\s)+)\s+"
71 r"(?P<severity>\S+)\s+"
75 r"\s*per-thread\s+context\s+switches.*"
78 r"(?P<thread_name>\S+)\s+\("
79 r"(?P<thread_id>\S+)\)\s+\S+\s+"
80 r"(?P<context_switches>[\d\.]+)"
83 r"\s*per-thread\s+page\s+faults.*"
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\.]+)"
93 r"(?P<thread_name>\S+)\s+\("
94 r"(?P<thread_id>\d+)\)\s*"
97 r"\s*instructions/packet,\s+cycles/packet\s+and\s+IPC.*"
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+"
110 r"\s*cache\s+hits\s+and\s+misses.*"
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\.]+)"
123 r"\s*load\s+operations.*"
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\.]+)"
135 r"\s*Branches,\s+branches\s+taken\s+and\s+mis-predictions.*"
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\.]+)"
147 r"\s*Thread\s+power\s+licensing.*"
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\.]+)"
158 r"\s*memory\s+reads\s+and\s+writes\s+per\s+memory\s+controller.*"
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\.]+)"
172 Creates a VPP object. This is the main object for defining a VPP program,
173 and interacting with its output.
175 def __init__(self, program, serializer, hook):
177 Initialize Bundle VPP class.
179 :param program: VPP instructions.
180 :param serializer: Metric serializer.
181 :param hook: VPP API socket.
183 :type serializer: Serializer
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
193 vpp_class.apidir = u"/usr/share/vpp/api"
194 self.obj = vpp_class(
199 logger=getLogger(__name__)
202 def attach(self, duration):
204 Attach events to VPP.
206 :param duration: Trial duration.
210 self.obj.connect(name=u"telemetry")
211 except (ConnectionRefusedError, OSError):
212 getLogger(__name__).error(u"Cannot connect to VPP!")
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))
226 self.obj.disconnect()
228 def fetch_data(self):
230 Fetch data by invoking API calls to VPP socket.
232 for command in self.api_command_list:
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)
240 if not isinstance(replies, list):
242 for reply in replies:
243 self.api_replies_list.append(reply)
244 reply = sub(r"\x1b[^m]*m", u"", reply.reply)
246 getLogger(__name__).info(reply)
248 getLogger(__name__).info(u"<no reply>")
249 self.serializer.create(metrics=self.metrics)
251 def process_data(self):
253 Post process command reply.
255 for command in zip(self.api_command_list, self.api_replies_list):
256 self_fn = command[0][u"api_args"][u"cmd"]
258 self_fn = getattr(self, self_fn.replace(u" ", u"_"))
259 self_fn(command[1].reply)
260 except AttributeError:
263 def show_interface(self, reply):
265 Parse the show interface output.
269 "name": "rx_packets",
277 :param reply: API reply.
280 for line in reply.splitlines():
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
301 def show_runtime(self, reply):
303 Parse the show runtime output.
309 "name": "virtio-input",
311 "thread_name": "vpp_wk_1",
318 :param reply: API reply.
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:
329 item[u"name"] = metric
330 labels[u"name"] = nodes[u"name"]
331 labels[u"state"] = nodes[u"state"]
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
346 def show_node_counters_verbose(self, reply):
348 Parse the show node conuter output.
352 "name": "node_counters",
354 "name": "dpdk-input",
355 "reason": "no_error",
357 "thread_name": "vpp_wk_1",
363 :param reply: API reply.
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:
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"]
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
390 def show_perfmon_statistics(self, reply):
392 Parse the permon output.
398 "name": "virtio-input",
400 "thread_name": "vpp_wk_1",
407 :param reply: API reply.
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:
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
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:
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
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:
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
460 reply = sub(r"\x1b[^m]*m", u"", reply)
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)
479 def show_version(self, reply):
481 Parse the version output.
487 "version": "v21.06-rc0~596-g1ca6c65e5~b1065",
492 :param reply: API reply.
495 for metric in self.serializer.metric_registry:
496 version = reply.split()[1]
499 item[u"name"] = metric
500 labels[u"version"] = version
501 item[u"labels"] = labels
503 self.serializer.serialize(
504 metric=metric, labels=labels, item=item