stats: Add name vectors to Python client
[vpp.git] / src / vpp-api / python / vpp_papi / vpp_stats.py
1 #!/usr/bin/env python
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     uint64_t error_value;
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 epoch;
53   uint64_t in_progress;
54   uint64_t directory_offset;
55   uint64_t error_offset;
56   uint64_t stats_offset;
57 } stat_segment_shared_header_t;
58
59 typedef struct
60 {
61   uint64_t current_epoch;
62   stat_segment_shared_header_t *shared_header;
63   stat_segment_directory_entry_t *directory_vector;
64   ssize_t memory_size;
65 } stat_client_main_t;
66
67 stat_client_main_t * stat_client_get(void);
68 void stat_client_free(stat_client_main_t * sm);
69 int stat_segment_connect_r (char *socket_name, stat_client_main_t * sm);
70 int stat_segment_connect (char *socket_name);
71 void stat_segment_disconnect_r (stat_client_main_t * sm);
72 void stat_segment_disconnect (void);
73
74 uint32_t *stat_segment_ls_r (uint8_t ** patterns, stat_client_main_t * sm);
75 uint32_t *stat_segment_ls (uint8_t ** pattern);
76 stat_segment_data_t *stat_segment_dump_r (uint32_t * stats, stat_client_main_t * sm);
77 stat_segment_data_t *stat_segment_dump (uint32_t * counter_vec);
78 void stat_segment_data_free (stat_segment_data_t * res);
79
80 double stat_segment_heartbeat_r (stat_client_main_t * sm);
81 int stat_segment_vec_len(void *vec);
82 uint8_t **stat_segment_string_vector(uint8_t **string_vector, char *string);
83 char *stat_segment_index_to_name_r (uint32_t index, stat_client_main_t * sm);
84 void free(void *ptr);
85 """)
86
87
88 # Utility functions
89 def make_string_vector(api, strings):
90     vec = ffi.NULL
91     if type(strings) is not list:
92         strings = [strings]
93     for s in strings:
94         vec = api.stat_segment_string_vector(vec, ffi.new("char []",
95                                                           s.encode('utf-8')))
96     return vec
97
98
99 def make_string_list(api, vec):
100     vec_len = api.stat_segment_vec_len(vec)
101     return [ffi.string(vec[i]) for i in range(vec_len)]
102
103
104 # 2-dimensonal array of thread, index
105 def simple_counter_vec_list(api, e):
106     vec = []
107     for thread in range(api.stat_segment_vec_len(e)):
108         len_interfaces = api.stat_segment_vec_len(e[thread])
109         if_per_thread = [e[thread][interfaces]
110                          for interfaces in range(len_interfaces)]
111         vec.append(if_per_thread)
112     return vec
113
114
115 def vlib_counter_dict(c):
116     return {'packets': c.packets,
117             'bytes': c.bytes}
118
119
120 def combined_counter_vec_list(api, e):
121     vec = []
122     for thread in range(api.stat_segment_vec_len(e)):
123         len_interfaces = api.stat_segment_vec_len(e[thread])
124         if_per_thread = [vlib_counter_dict(e[thread][interfaces])
125                          for interfaces in range(len_interfaces)]
126         vec.append(if_per_thread)
127     return vec
128
129 def name_vec_list(api, e):
130     return [ffi.string(e[i]).decode('utf-8') for i in range(api.stat_segment_vec_len(e)) if e[i] != ffi.NULL]
131
132 def stat_entry_to_python(api, e):
133     # Scalar index
134     if e.type == 1:
135         return e.scalar_value
136     if e.type == 2:
137         return simple_counter_vec_list(api, e.simple_counter_vec)
138     if e.type == 3:
139         return combined_counter_vec_list(api, e.combined_counter_vec)
140     if e.type == 4:
141         return e.error_value
142     if e.type == 5:
143         return name_vec_list(api, e.name_vector)
144     raise NotImplementedError()
145
146
147 class VPPStatsIOError(IOError):
148     message = "Stat segment client connection returned: " \
149               "%(retval)s %(strerror)s."
150
151     strerror = {-1: "Stat client couldn't open socket",
152                 -2: "Stat client socket open but couldn't connect",
153                 -3: "Receiving file descriptor failed",
154                 -4: "mmap fstat failed",
155                 -5: "mmap map failed"
156                 }
157
158     def __init__(self, message=None, **kwargs):
159         if 'retval' in kwargs:
160             self.retval = kwargs['retval']
161             kwargs['strerror'] = self.strerror[int(self.retval)]
162
163         if not message:
164             try:
165                 message = self.message % kwargs
166             except Exception as e:
167                 message = self.message
168         else:
169             message = message % kwargs
170
171         super(VPPStatsIOError, self).__init__(message)
172
173
174 class VPPStatsClientLoadError(RuntimeError):
175     pass
176
177
178 class VPPStats(object):
179     VPPStatsIOError = VPPStatsIOError
180
181     default_socketname = '/var/run/vpp/stats.sock'
182     sharedlib_name = 'libvppapiclient.so'
183
184     def __init__(self, socketname=default_socketname, timeout=10):
185         try:
186             self.api = ffi.dlopen(VPPStats.sharedlib_name)
187         except Exception:
188             raise VPPStatsClientLoadError("Could not open: %s" %
189                                           VPPStats.sharedlib_name)
190         self.client = self.api.stat_client_get()
191
192         poll_end_time = time.time() + timeout
193         while time.time() < poll_end_time:
194             rv = self.api.stat_segment_connect_r(socketname.encode('utf-8'),
195                                                  self.client)
196             if rv == 0:
197                 break
198
199         if rv != 0:
200             raise VPPStatsIOError(retval=rv)
201
202     def heartbeat(self):
203         return self.api.stat_segment_heartbeat_r(self.client)
204
205     def ls(self, patterns):
206         return self.api.stat_segment_ls_r(make_string_vector(self.api,
207                                                              patterns),
208                                           self.client)
209
210     def lsstr(self, patterns):
211         rv = self.api.stat_segment_ls_r(make_string_vector(self.api,
212                                                            patterns),
213                                         self.client)
214
215         if rv == ffi.NULL:
216             raise VPPStatsIOError()
217         return [ffi.string(self.api.stat_segment_index_to_name_r(rv[i], self.client)).decode('utf-8')
218                 for i in range(self.api.stat_segment_vec_len(rv))]
219
220     def dump(self, counters):
221         stats = {}
222         rv = self.api.stat_segment_dump_r(counters, self.client)
223         # Raise exception and retry
224         if rv == ffi.NULL:
225             raise VPPStatsIOError()
226         rv_len = self.api.stat_segment_vec_len(rv)
227
228         for i in range(rv_len):
229             n = ffi.string(rv[i].name).decode('utf-8')
230             e = stat_entry_to_python(self.api, rv[i])
231             if e is not None:
232                 stats[n] = e
233         return stats
234
235     def get_counter(self, name):
236         retries = 0
237         while True:
238             try:
239                 d = self.ls(name)
240                 s = self.dump(d)
241                 if len(s) > 1:
242                     raise AttributeError('Matches multiple counters {}'
243                                          .format(name))
244                 k, v = s.popitem()
245                 return v
246             except VPPStatsIOError as e:
247                 if retries > 10:
248                     return None
249                 retries += 1
250
251     def disconnect(self):
252         self.api.stat_segment_disconnect_r(self.client)
253         self.api.stat_client_free(self.client)
254
255     def set_errors(self):
256         '''Return all errors counters > 0'''
257         retries = 0
258         while True:
259             try:
260                 error_names = self.ls(['/err/'])
261                 error_counters = self.dump(error_names)
262                 break
263             except VPPStatsIOError as e:
264                 if retries > 10:
265                     return None
266                 retries += 1
267
268         return {k: error_counters[k]
269                 for k in error_counters.keys() if error_counters[k]}
270
271     def set_errors_str(self):
272         '''Return all errors counters > 0 pretty printed'''
273         s = 'ERRORS:\n'
274         error_counters = self.set_errors()
275         for k in sorted(error_counters):
276             s += '{:<60}{:>10}\n'.format(k, error_counters[k])
277         return s