blob: 0afed26215d432a7b98fc869330b6e9a40e044d4 [file] [log] [blame]
Ole Troan73202102018-08-31 00:29:48 +02001#!/usr/bin/env python
2
3from __future__ import print_function
4from cffi import FFI
Paul Vinciguerra56421092018-11-21 16:34:09 -08005import time
Ole Troan73202102018-08-31 00:29:48 +02006
7ffi = FFI()
8ffi.cdef("""
9typedef uint64_t counter_t;
10typedef struct {
11 counter_t packets;
12 counter_t bytes;
13} vlib_counter_t;
14
15typedef enum {
16 STAT_DIR_TYPE_ILLEGAL = 0,
Ole Troan58492a82018-09-04 13:19:12 +020017 STAT_DIR_TYPE_SCALAR_INDEX,
Ole Troan73202102018-08-31 00:29:48 +020018 STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE,
19 STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED,
20 STAT_DIR_TYPE_ERROR_INDEX,
Ole Troan73202102018-08-31 00:29:48 +020021} stat_directory_type_t;
22
Ole Troan58492a82018-09-04 13:19:12 +020023typedef struct
24{
Ole Troan73202102018-08-31 00:29:48 +020025 stat_directory_type_t type;
26 union {
Ole Troan58492a82018-09-04 13:19:12 +020027 uint64_t offset;
28 uint64_t index;
29 uint64_t value;
30 };
31 uint64_t offset_vector;
32 char name[128]; // TODO change this to pointer to "somewhere"
33} stat_segment_directory_entry_t;
34
35typedef struct
36{
37 char *name;
38 stat_directory_type_t type;
39 union
40 {
Ole Troan73202102018-08-31 00:29:48 +020041 double scalar_value;
42 uint64_t error_value;
Ole Troan73202102018-08-31 00:29:48 +020043 counter_t **simple_counter_vec;
44 vlib_counter_t **combined_counter_vec;
45 };
46} stat_segment_data_t;
47
Paul Vinciguerra56421092018-11-21 16:34:09 -080048typedef struct
49{
50 uint64_t epoch;
51 uint64_t in_progress;
52 uint64_t directory_offset;
53 uint64_t error_offset;
54 uint64_t stats_offset;
55} stat_segment_shared_header_t;
56
57typedef struct
58{
59 uint64_t current_epoch;
60 stat_segment_shared_header_t *shared_header;
61 stat_segment_directory_entry_t *directory_vector;
62 ssize_t memory_size;
63} stat_client_main_t;
64
65stat_client_main_t * stat_client_get(void);
66void stat_client_free(stat_client_main_t * sm);
67int stat_segment_connect_r (char *socket_name, stat_client_main_t * sm);
Ole Troan73202102018-08-31 00:29:48 +020068int stat_segment_connect (char *socket_name);
Paul Vinciguerra56421092018-11-21 16:34:09 -080069void stat_segment_disconnect_r (stat_client_main_t * sm);
Ole Troan73202102018-08-31 00:29:48 +020070void stat_segment_disconnect (void);
71
Paul Vinciguerra56421092018-11-21 16:34:09 -080072uint32_t *stat_segment_ls_r (uint8_t ** patterns, stat_client_main_t * sm);
Ole Troan58492a82018-09-04 13:19:12 +020073uint32_t *stat_segment_ls (uint8_t ** pattern);
Paul Vinciguerra56421092018-11-21 16:34:09 -080074stat_segment_data_t *stat_segment_dump_r (uint32_t * stats, stat_client_main_t * sm);
Ole Troan58492a82018-09-04 13:19:12 +020075stat_segment_data_t *stat_segment_dump (uint32_t * counter_vec);
Ole Troan73202102018-08-31 00:29:48 +020076void stat_segment_data_free (stat_segment_data_t * res);
Paul Vinciguerra56421092018-11-21 16:34:09 -080077
78double stat_segment_heartbeat_r (stat_client_main_t * sm);
Ole Troan73202102018-08-31 00:29:48 +020079double stat_segment_heartbeat (void);
80int stat_segment_vec_len(void *vec);
81uint8_t **stat_segment_string_vector(uint8_t **string_vector, char *string);
82""")
83
84
85# Utility functions
86def make_string_vector(api, strings):
87 vec = ffi.NULL
88 if type(strings) is not list:
89 strings = [strings]
90 for s in strings:
Ole Troan31555a32018-10-22 09:30:26 +020091 vec = api.stat_segment_string_vector(vec, ffi.new("char []",
92 s.encode()))
Ole Troan73202102018-08-31 00:29:48 +020093 return vec
94
95
96def make_string_list(api, vec):
97 vec_len = api.stat_segment_vec_len(vec)
98 return [ffi.string(vec[i]) for i in range(vec_len)]
99
100
101# 2-dimensonal array of thread, index
102def simple_counter_vec_list(api, e):
103 vec = []
104 for thread in range(api.stat_segment_vec_len(e)):
105 len_interfaces = api.stat_segment_vec_len(e[thread])
106 if_per_thread = [e[thread][interfaces]
107 for interfaces in range(len_interfaces)]
108 vec.append(if_per_thread)
109 return vec
110
111
112def vlib_counter_dict(c):
113 return {'packets': c.packets,
114 'bytes': c.bytes}
115
116
117def combined_counter_vec_list(api, e):
118 vec = []
119 for thread in range(api.stat_segment_vec_len(e)):
120 len_interfaces = api.stat_segment_vec_len(e[thread])
121 if_per_thread = [vlib_counter_dict(e[thread][interfaces])
122 for interfaces in range(len_interfaces)]
123 vec.append(if_per_thread)
124 return vec
125
126
127def stat_entry_to_python(api, e):
Ole Troan58492a82018-09-04 13:19:12 +0200128 # Scalar index
129 if e.type == 1:
130 return e.scalar_value
Ole Troan58492a82018-09-04 13:19:12 +0200131 if e.type == 2:
132 return simple_counter_vec_list(api, e.simple_counter_vec)
Ole Troan73202102018-08-31 00:29:48 +0200133 if e.type == 3:
Ole Troan73202102018-08-31 00:29:48 +0200134 return combined_counter_vec_list(api, e.combined_counter_vec)
Ole Troan58492a82018-09-04 13:19:12 +0200135 if e.type == 4:
Ole Troan73202102018-08-31 00:29:48 +0200136 return e.error_value
137 return None
138
139
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800140class VPPStatsIOError(IOError):
Paul Vinciguerra356824f2019-01-05 19:37:27 -0800141 message = "Stat segment client connection returned: " \
142 "%(retval)s %(strerror)s."
143
144 strerror = {-1: "Stat client couldn't open socket",
145 -2: "Stat client socket open but couldn't connect",
146 -3: "Receiving file descriptor failed",
147 -4: "mmap fstat failed",
148 -5: "mmap map failed"
149 }
150
151 def __init__(self, message=None, **kwargs):
152 if 'retval' in kwargs:
153 self.retval = kwargs['retval']
154 kwargs['strerror'] = self.strerror[int(self.retval)]
155
156 if not message:
157 try:
158 message = self.message % kwargs
159 except Exception as e:
160 message = self.message
161 else:
162 message = message % kwargs
163
164 super(VPPStatsIOError, self).__init__(message)
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800165
166
167class VPPStatsClientLoadError(RuntimeError):
168 pass
169
170
Paul Vinciguerra7e713f12018-11-26 12:04:48 -0800171class VPPStats(object):
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800172 VPPStatsIOError = VPPStatsIOError
173
174 default_socketname = '/var/run/stats.sock'
175 sharedlib_name = 'libvppapiclient.so'
176
177 def __init__(self, socketname=default_socketname, timeout=10):
Paul Vinciguerra56421092018-11-21 16:34:09 -0800178 try:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800179 self.api = ffi.dlopen(VPPStats.sharedlib_name)
Paul Vinciguerra56421092018-11-21 16:34:09 -0800180 except Exception:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800181 raise VPPStatsClientLoadError("Could not open: %s" %
182 VPPStats.sharedlib_name)
Paul Vinciguerra56421092018-11-21 16:34:09 -0800183 self.client = self.api.stat_client_get()
184
185 poll_end_time = time.time() + timeout
186 while time.time() < poll_end_time:
187 rv = self.api.stat_segment_connect_r(socketname.encode(),
188 self.client)
189 if rv == 0:
190 break
191
Ole Troan73202102018-08-31 00:29:48 +0200192 if rv != 0:
Paul Vinciguerra356824f2019-01-05 19:37:27 -0800193 raise VPPStatsIOError(retval=rv)
Ole Troan73202102018-08-31 00:29:48 +0200194
195 def heartbeat(self):
Paul Vinciguerra56421092018-11-21 16:34:09 -0800196 return self.api.stat_segment_heartbeat_r(self.client)
Ole Troan73202102018-08-31 00:29:48 +0200197
198 def ls(self, patterns):
Paul Vinciguerra56421092018-11-21 16:34:09 -0800199 return self.api.stat_segment_ls_r(make_string_vector(self.api,
200 patterns),
201 self.client)
Ole Troan73202102018-08-31 00:29:48 +0200202
203 def dump(self, counters):
204 stats = {}
Paul Vinciguerra56421092018-11-21 16:34:09 -0800205 rv = self.api.stat_segment_dump_r(counters, self.client)
Ole Troanb4603a72018-09-18 11:06:33 +0200206 # Raise exception and retry
207 if rv == ffi.NULL:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800208 raise VPPStatsIOError()
Ole Troan73202102018-08-31 00:29:48 +0200209 rv_len = self.api.stat_segment_vec_len(rv)
210 for i in range(rv_len):
Ole Troan45d5c872018-09-18 10:23:01 +0200211 n = ffi.string(rv[i].name).decode()
Ole Troan73202102018-08-31 00:29:48 +0200212 e = stat_entry_to_python(self.api, rv[i])
Ole Troan31555a32018-10-22 09:30:26 +0200213 if e is not None:
Ole Troan282093f2018-09-19 12:38:51 +0200214 stats[n] = e
Ole Troan73202102018-08-31 00:29:48 +0200215 return stats
216
Ole Troan58492a82018-09-04 13:19:12 +0200217 def get_counter(self, name):
Ole Troanb4603a72018-09-18 11:06:33 +0200218 retries = 0
219 while True:
220 try:
Ole Troan7f991832018-12-06 17:35:12 +0100221 d = self.ls(name)
222 s = self.dump(d)
223 if len(s) > 1:
224 raise AttributeError('Matches multiple counters {}'
225 .format(name))
226 k, v = s.popitem()
227 return v
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800228 except VPPStatsIOError as e:
Ole Troanb4603a72018-09-18 11:06:33 +0200229 if retries > 10:
230 return None
231 retries += 1
Ole Troan73202102018-08-31 00:29:48 +0200232
233 def disconnect(self):
Paul Vinciguerra56421092018-11-21 16:34:09 -0800234 self.api.stat_segment_disconnect_r(self.client)
235 self.api.stat_client_free(self.client)
Ole Troan73202102018-08-31 00:29:48 +0200236
237 def set_errors(self):
238 '''Return all errors counters > 0'''
Ole Troanb4603a72018-09-18 11:06:33 +0200239 retries = 0
240 while True:
241 try:
242 error_names = self.ls(['/err/'])
243 error_counters = self.dump(error_names)
244 break
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800245 except VPPStatsIOError as e:
Ole Troanb4603a72018-09-18 11:06:33 +0200246 if retries > 10:
247 return None
248 retries += 1
Paul Vinciguerra56421092018-11-21 16:34:09 -0800249
Ole Troan73202102018-08-31 00:29:48 +0200250 return {k: error_counters[k]
251 for k in error_counters.keys() if error_counters[k]}
252
253 def set_errors_str(self):
254 '''Return all errors counters > 0 pretty printed'''
255 s = 'ERRORS:\n'
256 error_counters = self.set_errors()
257 for k in sorted(error_counters):
258 s += '{:<60}{:>10}\n'.format(k, error_counters[k])
259 return s