blob: aa9ff85b3c70e3f4b91e6d72533822633af2031a [file] [log] [blame]
Renato Botelho do Coutoead1e532019-10-31 13:31:07 -05001#!/usr/bin/env python3
Ole Troane66443c2021-03-18 11:12:01 +01002#
3# Copyright (c) 2021 Cisco and/or its affiliates.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at:
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
Ole Troan73202102018-08-31 00:29:48 +020016
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020017"""
Ole Troane66443c2021-03-18 11:12:01 +010018This module implement Python access to the VPP statistics segment. It
19accesses the data structures directly in shared memory.
20VPP uses optimistic locking, so data structures may change underneath
21us while we are reading. Data is copied out and it's important to
22spend as little time as possible "holding the lock".
23
24Counters are stored in VPP as a two dimensional array.
25Index by thread and index (typically sw_if_index).
26Simple counters count only packets, Combined counters count packets
27and octets.
28
29Counters can be accessed in either dimension.
30stat['/if/rx'] - returns 2D lists
31stat['/if/rx'][0] - returns counters for all interfaces for thread 0
32stat['/if/rx'][0][1] - returns counter for interface 1 on thread 0
33stat['/if/rx'][0][1]['packets'] - returns the packet counter
34 for interface 1 on thread 0
35stat['/if/rx'][:, 1] - returns the counters for interface 1 on all threads
36stat['/if/rx'][:, 1].packets() - returns the packet counters for
37 interface 1 on all threads
38stat['/if/rx'][:, 1].sum_packets() - returns the sum of packet counters for
39 interface 1 on all threads
40stat['/if/rx-miss'][:, 1].sum() - returns the sum of packet counters for
41 interface 1 on all threads for simple counters
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020042"""
Ole Troane66443c2021-03-18 11:12:01 +010043
44import os
45import socket
46import array
47import mmap
48from struct import Struct
Paul Vinciguerra56421092018-11-21 16:34:09 -080049import time
Ole Troane66443c2021-03-18 11:12:01 +010050import unittest
51import re
Ole Troan73202102018-08-31 00:29:48 +020052
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020053
Ole Troane66443c2021-03-18 11:12:01 +010054def recv_fd(sock):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020055 """Get file descriptor for memory map"""
56 fds = array.array("i") # Array of ints
Tom Jonesb10561b2024-02-07 13:31:50 +000057 _, ancdata, _, _ = sock.recvmsg(0, socket.CMSG_SPACE(4))
Ole Troane66443c2021-03-18 11:12:01 +010058 for cmsg_level, cmsg_type, cmsg_data in ancdata:
59 if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020060 fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
Ole Troane66443c2021-03-18 11:12:01 +010061 return list(fds)[0]
Ole Troan73202102018-08-31 00:29:48 +020062
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020063
64VEC_LEN_FMT = Struct("I")
65
66
Ole Troane66443c2021-03-18 11:12:01 +010067def get_vec_len(stats, vector_offset):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020068 """Equivalent to VPP vec_len()"""
Ole Troane66443c2021-03-18 11:12:01 +010069 return VEC_LEN_FMT.unpack_from(stats.statseg, vector_offset - 8)[0]
Ole Troan73202102018-08-31 00:29:48 +020070
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020071
Ole Troane66443c2021-03-18 11:12:01 +010072def get_string(stats, ptr):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020073 """Get a string from a VPP vector"""
Ole Troane66443c2021-03-18 11:12:01 +010074 namevector = ptr - stats.base
75 namevectorlen = get_vec_len(stats, namevector)
76 if namevector + namevectorlen >= stats.size:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020077 raise IOError("String overruns stats segment")
78 return stats.statseg[namevector : namevector + namevectorlen - 1].decode("ascii")
Ole Troan73202102018-08-31 00:29:48 +020079
80
Ole Troane66443c2021-03-18 11:12:01 +010081class StatsVector:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020082 """A class representing a VPP vector"""
Ole Troan73202102018-08-31 00:29:48 +020083
Ole Troane66443c2021-03-18 11:12:01 +010084 def __init__(self, stats, ptr, fmt):
85 self.vec_start = ptr - stats.base
86 self.vec_len = get_vec_len(stats, ptr - stats.base)
87 self.struct = Struct(fmt)
88 self.fmtlen = len(fmt)
89 self.elementsize = self.struct.size
90 self.statseg = stats.statseg
91 self.stats = stats
Ole Troan73202102018-08-31 00:29:48 +020092
Ole Troane66443c2021-03-18 11:12:01 +010093 if self.vec_start + self.vec_len * self.elementsize >= stats.size:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020094 raise IOError("Vector overruns stats segment")
Ole Troan73202102018-08-31 00:29:48 +020095
Ole Troane66443c2021-03-18 11:12:01 +010096 def __iter__(self):
97 with self.stats.lock:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020098 return self.struct.iter_unpack(
99 self.statseg[
100 self.vec_start : self.vec_start + self.elementsize * self.vec_len
101 ]
102 )
Ole Troan73202102018-08-31 00:29:48 +0200103
Ole Troane66443c2021-03-18 11:12:01 +0100104 def __getitem__(self, index):
105 if index > self.vec_len:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200106 raise IOError("Index beyond end of vector")
Ole Troane66443c2021-03-18 11:12:01 +0100107 with self.stats.lock:
108 if self.fmtlen == 1:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200109 return self.struct.unpack_from(
110 self.statseg, self.vec_start + (index * self.elementsize)
111 )[0]
112 return self.struct.unpack_from(
113 self.statseg, self.vec_start + (index * self.elementsize)
114 )
Ole Troan73202102018-08-31 00:29:48 +0200115
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200116
117class VPPStats:
118 """Main class implementing Python access to the VPP statistics segment"""
119
Ole Troane66443c2021-03-18 11:12:01 +0100120 # pylint: disable=too-many-instance-attributes
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200121 shared_headerfmt = Struct("QPQQPP")
122 default_socketname = "/run/vpp/stats.sock"
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800123
124 def __init__(self, socketname=default_socketname, timeout=10):
Paul Vinciguerrae090f4d2019-11-29 17:41:20 -0500125 self.socketname = socketname
126 self.timeout = timeout
Ole Troane66443c2021-03-18 11:12:01 +0100127 self.directory = {}
128 self.lock = StatsLock(self)
Paul Vinciguerrae090f4d2019-11-29 17:41:20 -0500129 self.connected = False
Ole Troane66443c2021-03-18 11:12:01 +0100130 self.size = 0
131 self.last_epoch = 0
Ole Troane66443c2021-03-18 11:12:01 +0100132 self.statseg = 0
Paul Vinciguerrae090f4d2019-11-29 17:41:20 -0500133
134 def connect(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200135 """Connect to stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100136 if self.connected:
137 return
138 sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
Tom Jonesb3ee84d2024-02-07 13:34:56 +0000139
140 # Our connect races the corresponding recv_fds call in VPP, if we beat
141 # VPP then we will try (unsuccessfully) to receive file descriptors and
142 # will have gone away before VPP can respond to our connect. A short
143 # timeout here stops this error occurring.
144 sock.settimeout(1)
Ole Troane66443c2021-03-18 11:12:01 +0100145 sock.connect(self.socketname)
Paul Vinciguerra56421092018-11-21 16:34:09 -0800146
Ole Troane66443c2021-03-18 11:12:01 +0100147 mfd = recv_fd(sock)
148 sock.close()
Paul Vinciguerra56421092018-11-21 16:34:09 -0800149
Ole Troane66443c2021-03-18 11:12:01 +0100150 stat_result = os.fstat(mfd)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200151 self.statseg = mmap.mmap(
152 mfd, stat_result.st_size, mmap.PROT_READ, mmap.MAP_SHARED
153 )
Tianyu Li0e79bc12021-04-14 12:36:20 +0800154 os.close(mfd)
Ole Troan73202102018-08-31 00:29:48 +0200155
Ole Troane66443c2021-03-18 11:12:01 +0100156 self.size = stat_result.st_size
157 if self.version != 2:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200158 raise Exception("Incompatbile stat segment version {}".format(self.version))
Ole Troan73202102018-08-31 00:29:48 +0200159
Ole Troane66443c2021-03-18 11:12:01 +0100160 self.refresh()
161 self.connected = True
Ole Troan233e4682019-05-16 15:01:34 +0200162
Ole Troan73202102018-08-31 00:29:48 +0200163 def disconnect(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200164 """Disconnect from stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100165 if self.connected:
166 self.statseg.close()
Paul Vinciguerrae090f4d2019-11-29 17:41:20 -0500167 self.connected = False
Ole Troane66443c2021-03-18 11:12:01 +0100168
169 @property
170 def version(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200171 """Get version of stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100172 return self.shared_headerfmt.unpack_from(self.statseg)[0]
173
174 @property
175 def base(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200176 """Get base pointer of stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100177 return self.shared_headerfmt.unpack_from(self.statseg)[1]
178
179 @property
180 def epoch(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200181 """Get current epoch value from stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100182 return self.shared_headerfmt.unpack_from(self.statseg)[2]
183
184 @property
185 def in_progress(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200186 """Get value of in_progress from stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100187 return self.shared_headerfmt.unpack_from(self.statseg)[3]
188
189 @property
190 def directory_vector(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200191 """Get pointer of directory vector"""
Ole Troane66443c2021-03-18 11:12:01 +0100192 return self.shared_headerfmt.unpack_from(self.statseg)[4]
193
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200194 elementfmt = "IQ128s"
Ole Troane66443c2021-03-18 11:12:01 +0100195
Arthur de Kerhordb023802021-03-11 10:26:54 -0800196 def refresh(self, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200197 """Refresh directory vector cache (epoch changed)"""
Ole Troane66443c2021-03-18 11:12:01 +0100198 directory = {}
Arthur de Kerhordb023802021-03-11 10:26:54 -0800199 directory_by_idx = {}
200 while True:
201 try:
202 with self.lock:
Arthur de Kerhorc9ae8cf2021-06-24 19:39:44 +0200203 self.last_epoch = self.epoch
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200204 for i, direntry in enumerate(
205 StatsVector(self, self.directory_vector, self.elementfmt)
206 ):
207 path_raw = direntry[2].find(b"\x00")
208 path = direntry[2][:path_raw].decode("ascii")
Arthur de Kerhordb023802021-03-11 10:26:54 -0800209 directory[path] = StatsEntry(direntry[0], direntry[1])
210 directory_by_idx[i] = path
Arthur de Kerhordb023802021-03-11 10:26:54 -0800211 self.directory = directory
212 self.directory_by_idx = directory_by_idx
Arthur de Kerhor905c1532021-06-25 18:03:24 +0200213 return
Arthur de Kerhordb023802021-03-11 10:26:54 -0800214 except IOError:
215 if not blocking:
216 raise
Ole Troane66443c2021-03-18 11:12:01 +0100217
Arthur de Kerhordb023802021-03-11 10:26:54 -0800218 def __getitem__(self, item, blocking=True):
Ole Troane66443c2021-03-18 11:12:01 +0100219 if not self.connected:
220 self.connect()
Arthur de Kerhordb023802021-03-11 10:26:54 -0800221 while True:
222 try:
223 if self.last_epoch != self.epoch:
224 self.refresh(blocking)
225 with self.lock:
Arthur de Kerhor905c1532021-06-25 18:03:24 +0200226 return self.directory[item].get_counter(self)
Arthur de Kerhordb023802021-03-11 10:26:54 -0800227 except IOError:
228 if not blocking:
229 raise
Ole Troane66443c2021-03-18 11:12:01 +0100230
231 def __iter__(self):
232 return iter(self.directory.items())
Ole Troan73202102018-08-31 00:29:48 +0200233
Arthur de Kerhordb023802021-03-11 10:26:54 -0800234 def set_errors(self, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200235 """Return dictionary of error counters > 0"""
Ole Troane66443c2021-03-18 11:12:01 +0100236 if not self.connected:
237 self.connect()
Paul Vinciguerra56421092018-11-21 16:34:09 -0800238
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200239 errors = {k: v for k, v in self.directory.items() if k.startswith("/err/")}
Ole Troane66443c2021-03-18 11:12:01 +0100240 result = {}
Damjan Marion66c85832022-03-14 13:04:38 +0100241 for k in errors:
Arthur de Kerhordb023802021-03-11 10:26:54 -0800242 try:
Damjan Marion66c85832022-03-14 13:04:38 +0100243 total = self[k].sum()
244 if total:
245 result[k] = total
246 except KeyError:
247 pass
248 return result
Ole Troan73202102018-08-31 00:29:48 +0200249
Arthur de Kerhordb023802021-03-11 10:26:54 -0800250 def set_errors_str(self, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200251 """Return all errors counters > 0 pretty printed"""
252 error_string = ["ERRORS:"]
Arthur de Kerhordb023802021-03-11 10:26:54 -0800253 error_counters = self.set_errors(blocking)
Ole Troan73202102018-08-31 00:29:48 +0200254 for k in sorted(error_counters):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200255 error_string.append("{:<60}{:>10}".format(k, error_counters[k]))
256 return "%s\n" % "\n".join(error_string)
Ole Troane66443c2021-03-18 11:12:01 +0100257
Arthur de Kerhordb023802021-03-11 10:26:54 -0800258 def get_counter(self, name, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200259 """Alternative call to __getitem__"""
Arthur de Kerhordb023802021-03-11 10:26:54 -0800260 return self.__getitem__(name, blocking)
Ole Troane66443c2021-03-18 11:12:01 +0100261
Arthur de Kerhordb023802021-03-11 10:26:54 -0800262 def get_err_counter(self, name, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200263 """Alternative call to __getitem__"""
Damjan Marion66c85832022-03-14 13:04:38 +0100264 return self.__getitem__(name, blocking).sum()
Ole Troane66443c2021-03-18 11:12:01 +0100265
266 def ls(self, patterns):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200267 """Returns list of counters matching pattern"""
Ole Troane66443c2021-03-18 11:12:01 +0100268 # pylint: disable=invalid-name
269 if not self.connected:
270 self.connect()
Ole Troan3daca3f2021-03-29 21:12:53 +0200271 if not isinstance(patterns, list):
272 patterns = [patterns]
Ole Troane66443c2021-03-18 11:12:01 +0100273 regex = [re.compile(i) for i in patterns]
Ole Troan43eb6082021-08-31 10:47:45 +0200274 if self.last_epoch != self.epoch:
275 self.refresh()
276
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200277 return [
278 k
279 for k, v in self.directory.items()
280 if any(re.match(pattern, k) for pattern in regex)
281 ]
Ole Troane66443c2021-03-18 11:12:01 +0100282
Arthur de Kerhordb023802021-03-11 10:26:54 -0800283 def dump(self, counters, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200284 """Given a list of counters return a dictionary of results"""
Ole Troane66443c2021-03-18 11:12:01 +0100285 if not self.connected:
286 self.connect()
287 result = {}
288 for cnt in counters:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200289 result[cnt] = self.__getitem__(cnt, blocking)
Ole Troane66443c2021-03-18 11:12:01 +0100290 return result
291
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200292
293class StatsLock:
294 """Stat segment optimistic locking"""
Ole Troane66443c2021-03-18 11:12:01 +0100295
296 def __init__(self, stats):
297 self.stats = stats
298 self.epoch = 0
299
300 def __enter__(self):
301 acquired = self.acquire(blocking=True)
302 assert acquired, "Lock wasn't acquired, but blocking=True"
303 return self
304
305 def __exit__(self, exc_type=None, exc_value=None, traceback=None):
306 self.release()
307
308 def acquire(self, blocking=True, timeout=-1):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200309 """Acquire the lock. Await in progress to go false. Record epoch."""
Ole Troane66443c2021-03-18 11:12:01 +0100310 self.epoch = self.stats.epoch
311 if timeout > 0:
312 start = time.monotonic()
313 while self.stats.in_progress:
314 if not blocking:
315 time.sleep(0.01)
316 if timeout > 0:
317 if start + time.monotonic() > timeout:
318 return False
319 return True
320
321 def release(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200322 """Check if data read while locked is valid"""
Ole Troane66443c2021-03-18 11:12:01 +0100323 if self.stats.in_progress or self.stats.epoch != self.epoch:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200324 raise IOError("Optimistic lock failed, retry")
Ole Troane66443c2021-03-18 11:12:01 +0100325
326 def locked(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200327 """Not used"""
Ole Troane66443c2021-03-18 11:12:01 +0100328
329
330class StatsCombinedList(list):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200331 """Column slicing for Combined counters list"""
Ole Troane66443c2021-03-18 11:12:01 +0100332
333 def __getitem__(self, item):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200334 """Supports partial numpy style 2d support. Slice by column [:,1]"""
Ole Troane66443c2021-03-18 11:12:01 +0100335 if isinstance(item, int):
336 return list.__getitem__(self, item)
337 return CombinedList([row[item[1]] for row in self])
338
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200339
Ole Troane66443c2021-03-18 11:12:01 +0100340class CombinedList(list):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200341 """Combined Counters 2-dimensional by thread by index of packets/octets"""
Ole Troane66443c2021-03-18 11:12:01 +0100342
343 def packets(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200344 """Return column (2nd dimension). Packets for all threads"""
Ole Troane66443c2021-03-18 11:12:01 +0100345 return [pair[0] for pair in self]
346
347 def octets(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200348 """Return column (2nd dimension). Octets for all threads"""
Ole Troane66443c2021-03-18 11:12:01 +0100349 return [pair[1] for pair in self]
350
351 def sum_packets(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200352 """Return column (2nd dimension). Sum of all packets for all threads"""
Ole Troane66443c2021-03-18 11:12:01 +0100353 return sum(self.packets())
354
355 def sum_octets(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200356 """Return column (2nd dimension). Sum of all octets for all threads"""
Ole Troane66443c2021-03-18 11:12:01 +0100357 return sum(self.octets())
358
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200359
Ole Troane66443c2021-03-18 11:12:01 +0100360class StatsTuple(tuple):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200361 """A Combined vector tuple (packets, octets)"""
362
Ole Troane66443c2021-03-18 11:12:01 +0100363 def __init__(self, data):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200364 self.dictionary = {"packets": data[0], "bytes": data[1]}
Ole Troane66443c2021-03-18 11:12:01 +0100365 super().__init__()
366
367 def __repr__(self):
368 return dict.__repr__(self.dictionary)
369
370 def __getitem__(self, item):
371 if isinstance(item, int):
372 return tuple.__getitem__(self, item)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200373 if item == "packets":
Ole Troane66443c2021-03-18 11:12:01 +0100374 return tuple.__getitem__(self, 0)
375 return tuple.__getitem__(self, 1)
376
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200377
Ole Troane66443c2021-03-18 11:12:01 +0100378class StatsSimpleList(list):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200379 """Simple Counters 2-dimensional by thread by index of packets"""
Ole Troane66443c2021-03-18 11:12:01 +0100380
381 def __getitem__(self, item):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200382 """Supports partial numpy style 2d support. Slice by column [:,1]"""
Ole Troane66443c2021-03-18 11:12:01 +0100383 if isinstance(item, int):
384 return list.__getitem__(self, item)
385 return SimpleList([row[item[1]] for row in self])
386
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200387
Ole Troane66443c2021-03-18 11:12:01 +0100388class SimpleList(list):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200389 """Simple counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100390
391 def sum(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200392 """Sum the vector"""
Ole Troane66443c2021-03-18 11:12:01 +0100393 return sum(self)
394
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200395
396class StatsEntry:
397 """An individual stats entry"""
398
Ole Troane66443c2021-03-18 11:12:01 +0100399 # pylint: disable=unused-argument,no-self-use
400
401 def __init__(self, stattype, statvalue):
402 self.type = stattype
403 self.value = statvalue
404
405 if stattype == 1:
406 self.function = self.scalar
407 elif stattype == 2:
408 self.function = self.simple
409 elif stattype == 3:
410 self.function = self.combined
411 elif stattype == 4:
Ole Troane66443c2021-03-18 11:12:01 +0100412 self.function = self.name
Damjan Marion66c85832022-03-14 13:04:38 +0100413 elif stattype == 6:
Arthur de Kerhordb023802021-03-11 10:26:54 -0800414 self.function = self.symlink
Ole Troane66443c2021-03-18 11:12:01 +0100415 else:
416 self.function = self.illegal
417
418 def illegal(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200419 """Invalid or unknown counter type"""
Ole Troane66443c2021-03-18 11:12:01 +0100420 return None
421
422 def scalar(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200423 """Scalar counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100424 return self.value
425
426 def simple(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200427 """Simple counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100428 counter = StatsSimpleList()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200429 for threads in StatsVector(stats, self.value, "P"):
430 clist = [v[0] for v in StatsVector(stats, threads[0], "Q")]
Ole Troane66443c2021-03-18 11:12:01 +0100431 counter.append(clist)
432 return counter
433
434 def combined(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200435 """Combined counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100436 counter = StatsCombinedList()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200437 for threads in StatsVector(stats, self.value, "P"):
438 clist = [StatsTuple(cnt) for cnt in StatsVector(stats, threads[0], "QQ")]
Ole Troane66443c2021-03-18 11:12:01 +0100439 counter.append(clist)
440 return counter
441
Ole Troane66443c2021-03-18 11:12:01 +0100442 def name(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200443 """Name counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100444 counter = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200445 for name in StatsVector(stats, self.value, "P"):
Arthur de Kerhordb023802021-03-11 10:26:54 -0800446 if name[0]:
447 counter.append(get_string(stats, name[0]))
Ole Troane66443c2021-03-18 11:12:01 +0100448 return counter
449
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200450 SYMLINK_FMT1 = Struct("II")
451 SYMLINK_FMT2 = Struct("Q")
452
Arthur de Kerhordb023802021-03-11 10:26:54 -0800453 def symlink(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200454 """Symlink counter"""
Arthur de Kerhordb023802021-03-11 10:26:54 -0800455 b = self.SYMLINK_FMT2.pack(self.value)
456 index1, index2 = self.SYMLINK_FMT1.unpack(b)
457 name = stats.directory_by_idx[index1]
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200458 return stats[name][:, index2]
Arthur de Kerhordb023802021-03-11 10:26:54 -0800459
Ole Troane66443c2021-03-18 11:12:01 +0100460 def get_counter(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200461 """Return a list of counters"""
Arthur de Kerhordb023802021-03-11 10:26:54 -0800462 if stats:
463 return self.function(stats)
Ole Troane66443c2021-03-18 11:12:01 +0100464
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200465
Ole Troane66443c2021-03-18 11:12:01 +0100466class TestStats(unittest.TestCase):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200467 """Basic statseg tests"""
Ole Troane66443c2021-03-18 11:12:01 +0100468
469 def setUp(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200470 """Connect to statseg"""
Ole Troane66443c2021-03-18 11:12:01 +0100471 self.stat = VPPStats()
472 self.stat.connect()
473 self.profile = cProfile.Profile()
474 self.profile.enable()
475
476 def tearDown(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200477 """Disconnect from statseg"""
Ole Troane66443c2021-03-18 11:12:01 +0100478 self.stat.disconnect()
479 profile = Stats(self.profile)
480 profile.strip_dirs()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200481 profile.sort_stats("cumtime")
Ole Troane66443c2021-03-18 11:12:01 +0100482 profile.print_stats()
483 print("\n--->>>")
484
485 def test_counters(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200486 """Test access to statseg"""
Ole Troane66443c2021-03-18 11:12:01 +0100487
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200488 print("/err/abf-input-ip4/missed", self.stat["/err/abf-input-ip4/missed"])
489 print("/sys/heartbeat", self.stat["/sys/heartbeat"])
490 print("/if/names", self.stat["/if/names"])
491 print("/if/rx-miss", self.stat["/if/rx-miss"])
492 print("/if/rx-miss", self.stat["/if/rx-miss"][1])
493 print(
494 "/nat44-ed/out2in/slowpath/drops",
495 self.stat["/nat44-ed/out2in/slowpath/drops"],
496 )
Ole Troane66443c2021-03-18 11:12:01 +0100497 with self.assertRaises(KeyError):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200498 print("NO SUCH COUNTER", self.stat["foobar"])
499 print("/if/rx", self.stat.get_counter("/if/rx"))
500 print(
501 "/err/ethernet-input/no_error",
502 self.stat.get_counter("/err/ethernet-input/no_error"),
503 )
Ole Troane66443c2021-03-18 11:12:01 +0100504
505 def test_column(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200506 """Test column slicing"""
Ole Troane66443c2021-03-18 11:12:01 +0100507
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200508 print("/if/rx-miss", self.stat["/if/rx-miss"])
509 print("/if/rx", self.stat["/if/rx"]) # All interfaces for thread #1
510 print(
511 "/if/rx thread #1", self.stat["/if/rx"][0]
512 ) # All interfaces for thread #1
513 print(
514 "/if/rx thread #1, interface #1", self.stat["/if/rx"][0][1]
515 ) # All interfaces for thread #1
516 print("/if/rx if_index #1", self.stat["/if/rx"][:, 1])
517 print("/if/rx if_index #1 packets", self.stat["/if/rx"][:, 1].packets())
518 print("/if/rx if_index #1 packets", self.stat["/if/rx"][:, 1].sum_packets())
519 print("/if/rx if_index #1 packets", self.stat["/if/rx"][:, 1].octets())
520 print("/if/rx-miss", self.stat["/if/rx-miss"])
521 print("/if/rx-miss if_index #1 packets", self.stat["/if/rx-miss"][:, 1].sum())
522 print("/if/rx if_index #1 packets", self.stat["/if/rx"][0][1]["packets"])
Ole Troane66443c2021-03-18 11:12:01 +0100523
Ole Troane66443c2021-03-18 11:12:01 +0100524 def test_nat44(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200525 """Test the nat counters"""
Ole Troane66443c2021-03-18 11:12:01 +0100526
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200527 print("/nat44-ei/ha/del-event-recv", self.stat["/nat44-ei/ha/del-event-recv"])
528 print(
529 "/err/nat44-ei-ha/pkts-processed",
530 self.stat["/err/nat44-ei-ha/pkts-processed"].sum(),
531 )
Ole Troane66443c2021-03-18 11:12:01 +0100532
533 def test_legacy(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200534 """Legacy interface"""
Ole Troane66443c2021-03-18 11:12:01 +0100535 directory = self.stat.ls(["^/if", "/err/ip4-input", "/sys/node/ip4-input"])
536 data = self.stat.dump(directory)
537 print(data)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200538 print("Looking up sys node")
Ole Troane66443c2021-03-18 11:12:01 +0100539 directory = self.stat.ls(["^/sys/node"])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200540 print("Dumping sys node")
Ole Troane66443c2021-03-18 11:12:01 +0100541 data = self.stat.dump(directory)
542 print(data)
543 directory = self.stat.ls(["^/foobar"])
544 data = self.stat.dump(directory)
545 print(data)
546
Ole Troan3daca3f2021-03-29 21:12:53 +0200547 def test_sys_nodes(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200548 """Test /sys/nodes"""
549 counters = self.stat.ls("^/sys/node")
550 print("COUNTERS:", counters)
551 print("/sys/node", self.stat.dump(counters))
552 print("/net/route/to", self.stat["/net/route/to"])
Ole Troan3daca3f2021-03-29 21:12:53 +0200553
Arthur de Kerhordb023802021-03-11 10:26:54 -0800554 def test_symlink(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200555 """Symbolic links"""
556 print("/interface/local0/rx", self.stat["/interfaces/local0/rx"])
557 print("/sys/nodes/unix-epoll-input", self.stat["/nodes/unix-epoll-input/calls"])
Arthur de Kerhordb023802021-03-11 10:26:54 -0800558
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200559
560if __name__ == "__main__":
Ole Troane66443c2021-03-18 11:12:01 +0100561 import cProfile
562 from pstats import Stats
563
564 unittest.main()