blob: 8cbe737db92e3c152c7a08075cd6c1b66295729a [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)
139 sock.connect(self.socketname)
Paul Vinciguerra56421092018-11-21 16:34:09 -0800140
Ole Troane66443c2021-03-18 11:12:01 +0100141 mfd = recv_fd(sock)
142 sock.close()
Paul Vinciguerra56421092018-11-21 16:34:09 -0800143
Ole Troane66443c2021-03-18 11:12:01 +0100144 stat_result = os.fstat(mfd)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200145 self.statseg = mmap.mmap(
146 mfd, stat_result.st_size, mmap.PROT_READ, mmap.MAP_SHARED
147 )
Tianyu Li0e79bc12021-04-14 12:36:20 +0800148 os.close(mfd)
Ole Troan73202102018-08-31 00:29:48 +0200149
Ole Troane66443c2021-03-18 11:12:01 +0100150 self.size = stat_result.st_size
151 if self.version != 2:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200152 raise Exception("Incompatbile stat segment version {}".format(self.version))
Ole Troan73202102018-08-31 00:29:48 +0200153
Ole Troane66443c2021-03-18 11:12:01 +0100154 self.refresh()
155 self.connected = True
Ole Troan233e4682019-05-16 15:01:34 +0200156
Ole Troan73202102018-08-31 00:29:48 +0200157 def disconnect(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200158 """Disconnect from stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100159 if self.connected:
160 self.statseg.close()
Paul Vinciguerrae090f4d2019-11-29 17:41:20 -0500161 self.connected = False
Ole Troane66443c2021-03-18 11:12:01 +0100162
163 @property
164 def version(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200165 """Get version of stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100166 return self.shared_headerfmt.unpack_from(self.statseg)[0]
167
168 @property
169 def base(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200170 """Get base pointer of stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100171 return self.shared_headerfmt.unpack_from(self.statseg)[1]
172
173 @property
174 def epoch(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200175 """Get current epoch value from stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100176 return self.shared_headerfmt.unpack_from(self.statseg)[2]
177
178 @property
179 def in_progress(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200180 """Get value of in_progress from stats segment"""
Ole Troane66443c2021-03-18 11:12:01 +0100181 return self.shared_headerfmt.unpack_from(self.statseg)[3]
182
183 @property
184 def directory_vector(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200185 """Get pointer of directory vector"""
Ole Troane66443c2021-03-18 11:12:01 +0100186 return self.shared_headerfmt.unpack_from(self.statseg)[4]
187
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200188 elementfmt = "IQ128s"
Ole Troane66443c2021-03-18 11:12:01 +0100189
Arthur de Kerhordb023802021-03-11 10:26:54 -0800190 def refresh(self, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200191 """Refresh directory vector cache (epoch changed)"""
Ole Troane66443c2021-03-18 11:12:01 +0100192 directory = {}
Arthur de Kerhordb023802021-03-11 10:26:54 -0800193 directory_by_idx = {}
194 while True:
195 try:
196 with self.lock:
Arthur de Kerhorc9ae8cf2021-06-24 19:39:44 +0200197 self.last_epoch = self.epoch
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200198 for i, direntry in enumerate(
199 StatsVector(self, self.directory_vector, self.elementfmt)
200 ):
201 path_raw = direntry[2].find(b"\x00")
202 path = direntry[2][:path_raw].decode("ascii")
Arthur de Kerhordb023802021-03-11 10:26:54 -0800203 directory[path] = StatsEntry(direntry[0], direntry[1])
204 directory_by_idx[i] = path
Arthur de Kerhordb023802021-03-11 10:26:54 -0800205 self.directory = directory
206 self.directory_by_idx = directory_by_idx
Arthur de Kerhor905c1532021-06-25 18:03:24 +0200207 return
Arthur de Kerhordb023802021-03-11 10:26:54 -0800208 except IOError:
209 if not blocking:
210 raise
Ole Troane66443c2021-03-18 11:12:01 +0100211
Arthur de Kerhordb023802021-03-11 10:26:54 -0800212 def __getitem__(self, item, blocking=True):
Ole Troane66443c2021-03-18 11:12:01 +0100213 if not self.connected:
214 self.connect()
Arthur de Kerhordb023802021-03-11 10:26:54 -0800215 while True:
216 try:
217 if self.last_epoch != self.epoch:
218 self.refresh(blocking)
219 with self.lock:
Arthur de Kerhor905c1532021-06-25 18:03:24 +0200220 return self.directory[item].get_counter(self)
Arthur de Kerhordb023802021-03-11 10:26:54 -0800221 except IOError:
222 if not blocking:
223 raise
Ole Troane66443c2021-03-18 11:12:01 +0100224
225 def __iter__(self):
226 return iter(self.directory.items())
Ole Troan73202102018-08-31 00:29:48 +0200227
Arthur de Kerhordb023802021-03-11 10:26:54 -0800228 def set_errors(self, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200229 """Return dictionary of error counters > 0"""
Ole Troane66443c2021-03-18 11:12:01 +0100230 if not self.connected:
231 self.connect()
Paul Vinciguerra56421092018-11-21 16:34:09 -0800232
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200233 errors = {k: v for k, v in self.directory.items() if k.startswith("/err/")}
Ole Troane66443c2021-03-18 11:12:01 +0100234 result = {}
Damjan Marion66c85832022-03-14 13:04:38 +0100235 for k in errors:
Arthur de Kerhordb023802021-03-11 10:26:54 -0800236 try:
Damjan Marion66c85832022-03-14 13:04:38 +0100237 total = self[k].sum()
238 if total:
239 result[k] = total
240 except KeyError:
241 pass
242 return result
Ole Troan73202102018-08-31 00:29:48 +0200243
Arthur de Kerhordb023802021-03-11 10:26:54 -0800244 def set_errors_str(self, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200245 """Return all errors counters > 0 pretty printed"""
246 error_string = ["ERRORS:"]
Arthur de Kerhordb023802021-03-11 10:26:54 -0800247 error_counters = self.set_errors(blocking)
Ole Troan73202102018-08-31 00:29:48 +0200248 for k in sorted(error_counters):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200249 error_string.append("{:<60}{:>10}".format(k, error_counters[k]))
250 return "%s\n" % "\n".join(error_string)
Ole Troane66443c2021-03-18 11:12:01 +0100251
Arthur de Kerhordb023802021-03-11 10:26:54 -0800252 def get_counter(self, name, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200253 """Alternative call to __getitem__"""
Arthur de Kerhordb023802021-03-11 10:26:54 -0800254 return self.__getitem__(name, blocking)
Ole Troane66443c2021-03-18 11:12:01 +0100255
Arthur de Kerhordb023802021-03-11 10:26:54 -0800256 def get_err_counter(self, name, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200257 """Alternative call to __getitem__"""
Damjan Marion66c85832022-03-14 13:04:38 +0100258 return self.__getitem__(name, blocking).sum()
Ole Troane66443c2021-03-18 11:12:01 +0100259
260 def ls(self, patterns):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200261 """Returns list of counters matching pattern"""
Ole Troane66443c2021-03-18 11:12:01 +0100262 # pylint: disable=invalid-name
263 if not self.connected:
264 self.connect()
Ole Troan3daca3f2021-03-29 21:12:53 +0200265 if not isinstance(patterns, list):
266 patterns = [patterns]
Ole Troane66443c2021-03-18 11:12:01 +0100267 regex = [re.compile(i) for i in patterns]
Ole Troan43eb6082021-08-31 10:47:45 +0200268 if self.last_epoch != self.epoch:
269 self.refresh()
270
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200271 return [
272 k
273 for k, v in self.directory.items()
274 if any(re.match(pattern, k) for pattern in regex)
275 ]
Ole Troane66443c2021-03-18 11:12:01 +0100276
Arthur de Kerhordb023802021-03-11 10:26:54 -0800277 def dump(self, counters, blocking=True):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200278 """Given a list of counters return a dictionary of results"""
Ole Troane66443c2021-03-18 11:12:01 +0100279 if not self.connected:
280 self.connect()
281 result = {}
282 for cnt in counters:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200283 result[cnt] = self.__getitem__(cnt, blocking)
Ole Troane66443c2021-03-18 11:12:01 +0100284 return result
285
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200286
287class StatsLock:
288 """Stat segment optimistic locking"""
Ole Troane66443c2021-03-18 11:12:01 +0100289
290 def __init__(self, stats):
291 self.stats = stats
292 self.epoch = 0
293
294 def __enter__(self):
295 acquired = self.acquire(blocking=True)
296 assert acquired, "Lock wasn't acquired, but blocking=True"
297 return self
298
299 def __exit__(self, exc_type=None, exc_value=None, traceback=None):
300 self.release()
301
302 def acquire(self, blocking=True, timeout=-1):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200303 """Acquire the lock. Await in progress to go false. Record epoch."""
Ole Troane66443c2021-03-18 11:12:01 +0100304 self.epoch = self.stats.epoch
305 if timeout > 0:
306 start = time.monotonic()
307 while self.stats.in_progress:
308 if not blocking:
309 time.sleep(0.01)
310 if timeout > 0:
311 if start + time.monotonic() > timeout:
312 return False
313 return True
314
315 def release(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200316 """Check if data read while locked is valid"""
Ole Troane66443c2021-03-18 11:12:01 +0100317 if self.stats.in_progress or self.stats.epoch != self.epoch:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200318 raise IOError("Optimistic lock failed, retry")
Ole Troane66443c2021-03-18 11:12:01 +0100319
320 def locked(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200321 """Not used"""
Ole Troane66443c2021-03-18 11:12:01 +0100322
323
324class StatsCombinedList(list):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200325 """Column slicing for Combined counters list"""
Ole Troane66443c2021-03-18 11:12:01 +0100326
327 def __getitem__(self, item):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200328 """Supports partial numpy style 2d support. Slice by column [:,1]"""
Ole Troane66443c2021-03-18 11:12:01 +0100329 if isinstance(item, int):
330 return list.__getitem__(self, item)
331 return CombinedList([row[item[1]] for row in self])
332
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200333
Ole Troane66443c2021-03-18 11:12:01 +0100334class CombinedList(list):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200335 """Combined Counters 2-dimensional by thread by index of packets/octets"""
Ole Troane66443c2021-03-18 11:12:01 +0100336
337 def packets(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200338 """Return column (2nd dimension). Packets for all threads"""
Ole Troane66443c2021-03-18 11:12:01 +0100339 return [pair[0] for pair in self]
340
341 def octets(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200342 """Return column (2nd dimension). Octets for all threads"""
Ole Troane66443c2021-03-18 11:12:01 +0100343 return [pair[1] for pair in self]
344
345 def sum_packets(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200346 """Return column (2nd dimension). Sum of all packets for all threads"""
Ole Troane66443c2021-03-18 11:12:01 +0100347 return sum(self.packets())
348
349 def sum_octets(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200350 """Return column (2nd dimension). Sum of all octets for all threads"""
Ole Troane66443c2021-03-18 11:12:01 +0100351 return sum(self.octets())
352
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200353
Ole Troane66443c2021-03-18 11:12:01 +0100354class StatsTuple(tuple):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200355 """A Combined vector tuple (packets, octets)"""
356
Ole Troane66443c2021-03-18 11:12:01 +0100357 def __init__(self, data):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200358 self.dictionary = {"packets": data[0], "bytes": data[1]}
Ole Troane66443c2021-03-18 11:12:01 +0100359 super().__init__()
360
361 def __repr__(self):
362 return dict.__repr__(self.dictionary)
363
364 def __getitem__(self, item):
365 if isinstance(item, int):
366 return tuple.__getitem__(self, item)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200367 if item == "packets":
Ole Troane66443c2021-03-18 11:12:01 +0100368 return tuple.__getitem__(self, 0)
369 return tuple.__getitem__(self, 1)
370
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200371
Ole Troane66443c2021-03-18 11:12:01 +0100372class StatsSimpleList(list):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200373 """Simple Counters 2-dimensional by thread by index of packets"""
Ole Troane66443c2021-03-18 11:12:01 +0100374
375 def __getitem__(self, item):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200376 """Supports partial numpy style 2d support. Slice by column [:,1]"""
Ole Troane66443c2021-03-18 11:12:01 +0100377 if isinstance(item, int):
378 return list.__getitem__(self, item)
379 return SimpleList([row[item[1]] for row in self])
380
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200381
Ole Troane66443c2021-03-18 11:12:01 +0100382class SimpleList(list):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200383 """Simple counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100384
385 def sum(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200386 """Sum the vector"""
Ole Troane66443c2021-03-18 11:12:01 +0100387 return sum(self)
388
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200389
390class StatsEntry:
391 """An individual stats entry"""
392
Ole Troane66443c2021-03-18 11:12:01 +0100393 # pylint: disable=unused-argument,no-self-use
394
395 def __init__(self, stattype, statvalue):
396 self.type = stattype
397 self.value = statvalue
398
399 if stattype == 1:
400 self.function = self.scalar
401 elif stattype == 2:
402 self.function = self.simple
403 elif stattype == 3:
404 self.function = self.combined
405 elif stattype == 4:
Ole Troane66443c2021-03-18 11:12:01 +0100406 self.function = self.name
Damjan Marion66c85832022-03-14 13:04:38 +0100407 elif stattype == 6:
Arthur de Kerhordb023802021-03-11 10:26:54 -0800408 self.function = self.symlink
Ole Troane66443c2021-03-18 11:12:01 +0100409 else:
410 self.function = self.illegal
411
412 def illegal(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200413 """Invalid or unknown counter type"""
Ole Troane66443c2021-03-18 11:12:01 +0100414 return None
415
416 def scalar(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200417 """Scalar counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100418 return self.value
419
420 def simple(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200421 """Simple counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100422 counter = StatsSimpleList()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200423 for threads in StatsVector(stats, self.value, "P"):
424 clist = [v[0] for v in StatsVector(stats, threads[0], "Q")]
Ole Troane66443c2021-03-18 11:12:01 +0100425 counter.append(clist)
426 return counter
427
428 def combined(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200429 """Combined counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100430 counter = StatsCombinedList()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200431 for threads in StatsVector(stats, self.value, "P"):
432 clist = [StatsTuple(cnt) for cnt in StatsVector(stats, threads[0], "QQ")]
Ole Troane66443c2021-03-18 11:12:01 +0100433 counter.append(clist)
434 return counter
435
Ole Troane66443c2021-03-18 11:12:01 +0100436 def name(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200437 """Name counter"""
Ole Troane66443c2021-03-18 11:12:01 +0100438 counter = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200439 for name in StatsVector(stats, self.value, "P"):
Arthur de Kerhordb023802021-03-11 10:26:54 -0800440 if name[0]:
441 counter.append(get_string(stats, name[0]))
Ole Troane66443c2021-03-18 11:12:01 +0100442 return counter
443
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200444 SYMLINK_FMT1 = Struct("II")
445 SYMLINK_FMT2 = Struct("Q")
446
Arthur de Kerhordb023802021-03-11 10:26:54 -0800447 def symlink(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200448 """Symlink counter"""
Arthur de Kerhordb023802021-03-11 10:26:54 -0800449 b = self.SYMLINK_FMT2.pack(self.value)
450 index1, index2 = self.SYMLINK_FMT1.unpack(b)
451 name = stats.directory_by_idx[index1]
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200452 return stats[name][:, index2]
Arthur de Kerhordb023802021-03-11 10:26:54 -0800453
Ole Troane66443c2021-03-18 11:12:01 +0100454 def get_counter(self, stats):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200455 """Return a list of counters"""
Arthur de Kerhordb023802021-03-11 10:26:54 -0800456 if stats:
457 return self.function(stats)
Ole Troane66443c2021-03-18 11:12:01 +0100458
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200459
Ole Troane66443c2021-03-18 11:12:01 +0100460class TestStats(unittest.TestCase):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200461 """Basic statseg tests"""
Ole Troane66443c2021-03-18 11:12:01 +0100462
463 def setUp(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200464 """Connect to statseg"""
Ole Troane66443c2021-03-18 11:12:01 +0100465 self.stat = VPPStats()
466 self.stat.connect()
467 self.profile = cProfile.Profile()
468 self.profile.enable()
469
470 def tearDown(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200471 """Disconnect from statseg"""
Ole Troane66443c2021-03-18 11:12:01 +0100472 self.stat.disconnect()
473 profile = Stats(self.profile)
474 profile.strip_dirs()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200475 profile.sort_stats("cumtime")
Ole Troane66443c2021-03-18 11:12:01 +0100476 profile.print_stats()
477 print("\n--->>>")
478
479 def test_counters(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200480 """Test access to statseg"""
Ole Troane66443c2021-03-18 11:12:01 +0100481
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200482 print("/err/abf-input-ip4/missed", self.stat["/err/abf-input-ip4/missed"])
483 print("/sys/heartbeat", self.stat["/sys/heartbeat"])
484 print("/if/names", self.stat["/if/names"])
485 print("/if/rx-miss", self.stat["/if/rx-miss"])
486 print("/if/rx-miss", self.stat["/if/rx-miss"][1])
487 print(
488 "/nat44-ed/out2in/slowpath/drops",
489 self.stat["/nat44-ed/out2in/slowpath/drops"],
490 )
Ole Troane66443c2021-03-18 11:12:01 +0100491 with self.assertRaises(KeyError):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200492 print("NO SUCH COUNTER", self.stat["foobar"])
493 print("/if/rx", self.stat.get_counter("/if/rx"))
494 print(
495 "/err/ethernet-input/no_error",
496 self.stat.get_counter("/err/ethernet-input/no_error"),
497 )
Ole Troane66443c2021-03-18 11:12:01 +0100498
499 def test_column(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200500 """Test column slicing"""
Ole Troane66443c2021-03-18 11:12:01 +0100501
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200502 print("/if/rx-miss", self.stat["/if/rx-miss"])
503 print("/if/rx", self.stat["/if/rx"]) # All interfaces for thread #1
504 print(
505 "/if/rx thread #1", self.stat["/if/rx"][0]
506 ) # All interfaces for thread #1
507 print(
508 "/if/rx thread #1, interface #1", self.stat["/if/rx"][0][1]
509 ) # All interfaces for thread #1
510 print("/if/rx if_index #1", self.stat["/if/rx"][:, 1])
511 print("/if/rx if_index #1 packets", self.stat["/if/rx"][:, 1].packets())
512 print("/if/rx if_index #1 packets", self.stat["/if/rx"][:, 1].sum_packets())
513 print("/if/rx if_index #1 packets", self.stat["/if/rx"][:, 1].octets())
514 print("/if/rx-miss", self.stat["/if/rx-miss"])
515 print("/if/rx-miss if_index #1 packets", self.stat["/if/rx-miss"][:, 1].sum())
516 print("/if/rx if_index #1 packets", self.stat["/if/rx"][0][1]["packets"])
Ole Troane66443c2021-03-18 11:12:01 +0100517
Ole Troane66443c2021-03-18 11:12:01 +0100518 def test_nat44(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200519 """Test the nat counters"""
Ole Troane66443c2021-03-18 11:12:01 +0100520
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200521 print("/nat44-ei/ha/del-event-recv", self.stat["/nat44-ei/ha/del-event-recv"])
522 print(
523 "/err/nat44-ei-ha/pkts-processed",
524 self.stat["/err/nat44-ei-ha/pkts-processed"].sum(),
525 )
Ole Troane66443c2021-03-18 11:12:01 +0100526
527 def test_legacy(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200528 """Legacy interface"""
Ole Troane66443c2021-03-18 11:12:01 +0100529 directory = self.stat.ls(["^/if", "/err/ip4-input", "/sys/node/ip4-input"])
530 data = self.stat.dump(directory)
531 print(data)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200532 print("Looking up sys node")
Ole Troane66443c2021-03-18 11:12:01 +0100533 directory = self.stat.ls(["^/sys/node"])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200534 print("Dumping sys node")
Ole Troane66443c2021-03-18 11:12:01 +0100535 data = self.stat.dump(directory)
536 print(data)
537 directory = self.stat.ls(["^/foobar"])
538 data = self.stat.dump(directory)
539 print(data)
540
Ole Troan3daca3f2021-03-29 21:12:53 +0200541 def test_sys_nodes(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200542 """Test /sys/nodes"""
543 counters = self.stat.ls("^/sys/node")
544 print("COUNTERS:", counters)
545 print("/sys/node", self.stat.dump(counters))
546 print("/net/route/to", self.stat["/net/route/to"])
Ole Troan3daca3f2021-03-29 21:12:53 +0200547
Arthur de Kerhordb023802021-03-11 10:26:54 -0800548 def test_symlink(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200549 """Symbolic links"""
550 print("/interface/local0/rx", self.stat["/interfaces/local0/rx"])
551 print("/sys/nodes/unix-epoll-input", self.stat["/nodes/unix-epoll-input/calls"])
Arthur de Kerhordb023802021-03-11 10:26:54 -0800552
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200553
554if __name__ == "__main__":
Ole Troane66443c2021-03-18 11:12:01 +0100555 import cProfile
556 from pstats import Stats
557
558 unittest.main()