misc: Fix python scripts shebang line
[vpp.git] / src / vpp-api / python / vpp_papi / vpp_stats.py
1 #!/usr/bin/env python3
2
3 from __future__ import print_function
4 from cffi import FFI
5 import time
6
7 ffi = FFI()
8 ffi.cdef("""
9 typedef uint64_t counter_t;
10 typedef struct {
11   counter_t packets;
12   counter_t bytes;
13 } vlib_counter_t;
14
15 typedef enum {
16   STAT_DIR_TYPE_ILLEGAL = 0,
17   STAT_DIR_TYPE_SCALAR_INDEX,
18   STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE,
19   STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED,
20   STAT_DIR_TYPE_ERROR_INDEX,
21   STAT_DIR_TYPE_NAME_VECTOR,
22 } stat_directory_type_t;
23
24 typedef struct
25 {
26   stat_directory_type_t type;
27   union {
28     uint64_t offset;
29     uint64_t index;
30     uint64_t value;
31   };
32   uint64_t offset_vector;
33   char name[128]; // TODO change this to pointer to "somewhere"
34 } stat_segment_directory_entry_t;
35
36 typedef struct
37 {
38   char *name;
39   stat_directory_type_t type;
40   union
41   {
42     double scalar_value;
43     counter_t *error_vector;
44     counter_t **simple_counter_vec;
45     vlib_counter_t **combined_counter_vec;
46     uint8_t **name_vector;
47   };
48 } stat_segment_data_t;
49
50 typedef struct
51 {
52   uint64_t version;
53   uint64_t epoch;
54   uint64_t in_progress;
55   uint64_t directory_offset;
56   uint64_t error_offset;
57   uint64_t stats_offset;
58 } stat_segment_shared_header_t;
59
60 typedef struct
61 {
62   uint64_t current_epoch;
63   stat_segment_shared_header_t *shared_header;
64   stat_segment_directory_entry_t *directory_vector;
65   ssize_t memory_size;
66 } stat_client_main_t;
67
68 stat_client_main_t * stat_client_get(void);
69 void stat_client_free(stat_client_main_t * sm);
70 int stat_segment_connect_r (char *socket_name, stat_client_main_t * sm);
71 int stat_segment_connect (char *socket_name);
72 void stat_segment_disconnect_r (stat_client_main_t * sm);
73 void stat_segment_disconnect (void);
74
75 uint32_t *stat_segment_ls_r (uint8_t ** patterns, stat_client_main_t * sm);
76 uint32_t *stat_segment_ls (uint8_t ** pattern);
77 stat_segment_data_t *stat_segment_dump_r (uint32_t * stats,
78                                           stat_client_main_t * sm);
79 stat_segment_data_t *stat_segment_dump (uint32_t * counter_vec);
80 void stat_segment_data_free (stat_segment_data_t * res);
81
82 double stat_segment_heartbeat_r (stat_client_main_t * sm);
83 int stat_segment_vec_len(void *vec);
84 uint8_t **stat_segment_string_vector(uint8_t **string_vector, char *string);
85 char *stat_segment_index_to_name_r (uint32_t index, stat_client_main_t * sm);
86 uint64_t stat_segment_version(void);
87 uint64_t stat_segment_version_r(stat_client_main_t *sm);
88 void free(void *ptr);
89 """)  # noqa: E501
90
91
92 # Utility functions
93 def make_string_vector(api, strings):
94     vec = ffi.NULL
95     if type(strings) is not list:
96         strings = [strings]
97     for s in strings:
98         vec = api.stat_segment_string_vector(vec, ffi.new("char []",
99                                                           s.encode('utf-8')))
100     return vec
101
102
103 def make_string_list(api, vec):
104     vec_len = api.stat_segment_vec_len(vec)
105     return [ffi.string(vec[i]) for i in range(vec_len)]
106
107
108 # 2-dimensonal array of thread, index
109 def simple_counter_vec_list(api, e):
110     vec = []
111     for thread in range(api.stat_segment_vec_len(e)):
112         len_interfaces = api.stat_segment_vec_len(e[thread])
113         if_per_thread = [e[thread][interfaces]
114                          for interfaces in range(len_interfaces)]
115         vec.append(if_per_thread)
116     return vec
117
118
119 def vlib_counter_dict(c):
120     return {'packets': c.packets,
121             'bytes': c.bytes}
122
123
124 def combined_counter_vec_list(api, e):
125     vec = []
126     for thread in range(api.stat_segment_vec_len(e)):
127         len_interfaces = api.stat_segment_vec_len(e[thread])
128         if_per_thread = [vlib_counter_dict(e[thread][interfaces])
129                          for interfaces in range(len_interfaces)]
130         vec.append(if_per_thread)
131     return vec
132
133
134 def error_vec_list(api, e):
135     vec = []
136     for thread in range(api.stat_segment_vec_len(e)):
137         vec.append(e[thread])
138     return vec
139
140
141 def name_vec_list(api, e):
142     return [ffi.string(e[i]).decode('utf-8') for i in
143             range(api.stat_segment_vec_len(e)) if e[i] != ffi.NULL]
144
145
146 def stat_entry_to_python(api, e):
147     # Scalar index
148     if e.type == 1:
149         return e.scalar_value
150     if e.type == 2:
151         return simple_counter_vec_list(api, e.simple_counter_vec)
152     if e.type == 3:
153         return combined_counter_vec_list(api, e.combined_counter_vec)
154     if e.type == 4:
155         return error_vec_list(api, e.error_vector)
156     if e.type == 5:
157         return name_vec_list(api, e.name_vector)
158     raise NotImplementedError()
159
160
161 class VPPStatsIOError(IOError):
162     message = "Stat segment client connection returned: " \
163               "%(retval)s %(strerror)s."
164
165     strerror = {-1: "Stat client couldn't open socket",
166                 -2: "Stat client socket open but couldn't connect",
167                 -3: "Receiving file descriptor failed",
168                 -4: "mmap fstat failed",
169                 -5: "mmap map failed"
170                 }
171
172     def __init__(self, message=None, **kwargs):
173         if 'retval' in kwargs:
174             self.retval = kwargs['retval']
175             kwargs['strerror'] = self.strerror[int(self.retval)]
176
177         if not message:
178             try:
179                 message = self.message % kwargs
180             except Exception:
181                 message = self.message
182         else:
183             message = message % kwargs
184
185         super(VPPStatsIOError, self).__init__(message)
186
187
188 class VPPStatsClientLoadError(RuntimeError):
189     pass
190
191
192 class VPPStats(object):
193     VPPStatsIOError = VPPStatsIOError
194
195     default_socketname = '/run/vpp/stats.sock'
196     sharedlib_name = 'libvppapiclient.so'
197
198     def __init__(self, socketname=default_socketname, timeout=10):
199         try:
200             self.api = ffi.dlopen(VPPStats.sharedlib_name)
201         except Exception:
202             raise VPPStatsClientLoadError("Could not open: %s" %
203                                           VPPStats.sharedlib_name)
204         self.client = self.api.stat_client_get()
205
206         poll_end_time = time.time() + timeout
207         while time.time() < poll_end_time:
208             rv = self.api.stat_segment_connect_r(socketname.encode('utf-8'),
209                                                  self.client)
210             # Break out if success or any other error than "no such file"
211             # (indicating that VPP hasn't started yet)
212             if rv == 0 or ffi.errno != 2:
213                 break
214
215         if rv != 0:
216             raise VPPStatsIOError(retval=rv)
217
218     def heartbeat(self):
219         return self.api.stat_segment_heartbeat_r(self.client)
220
221     def ls(self, patterns):
222         return self.api.stat_segment_ls_r(make_string_vector(self.api,
223                                                              patterns),
224                                           self.client)
225
226     def lsstr(self, patterns):
227         rv = self.api.stat_segment_ls_r(make_string_vector(self.api,
228                                                            patterns),
229                                         self.client)
230
231         if rv == ffi.NULL:
232             raise VPPStatsIOError()
233         return [ffi.string(self.api.stat_segment_index_to_name_r(
234             rv[i], self.client)).decode('utf-8')
235                 for i in range(self.api.stat_segment_vec_len(rv))]
236
237     def dump(self, counters):
238         stats = {}
239         rv = self.api.stat_segment_dump_r(counters, self.client)
240         # Raise exception and retry
241         if rv == ffi.NULL:
242             raise VPPStatsIOError()
243         rv_len = self.api.stat_segment_vec_len(rv)
244
245         for i in range(rv_len):
246             n = ffi.string(rv[i].name).decode('utf-8')
247             e = stat_entry_to_python(self.api, rv[i])
248             if e is not None:
249                 stats[n] = e
250         return stats
251
252     def get_counter(self, name):
253         retries = 0
254         while True:
255             try:
256                 d = self.ls(name)
257                 s = self.dump(d)
258                 if len(s) > 1:
259                     raise AttributeError('Matches multiple counters {}'
260                                          .format(name))
261                 k, v = s.popitem()
262                 return v
263             except VPPStatsIOError:
264                 if retries > 10:
265                     return None
266                 retries += 1
267
268     def get_err_counter(self, name):
269         """Get an error counter. The errors from each worker thread
270            are summed"""
271         return sum(self.get_counter(name))
272
273     def disconnect(self):
274         self.api.stat_segment_disconnect_r(self.client)
275         self.api.stat_client_free(self.client)
276
277     def set_errors(self):
278         '''Return all errors counters > 0'''
279         retries = 0
280         while True:
281             try:
282                 error_names = self.ls(['/err/'])
283                 error_counters = self.dump(error_names)
284                 break
285             except VPPStatsIOError:
286                 if retries > 10:
287                     return None
288                 retries += 1
289
290         return {k: sum(error_counters[k])
291                 for k in error_counters.keys() if sum(error_counters[k])}
292
293     def set_errors_str(self):
294         '''Return all errors counters > 0 pretty printed'''
295         s = ['ERRORS:']
296         error_counters = self.set_errors()
297         for k in sorted(error_counters):
298             s.append('{:<60}{:>10}'.format(k, error_counters[k]))
299         return '%s\n' % '\n'.join(s)