STATS: Python binding to access VPP statistics and counters.
[vpp.git] / src / vpp-api / client / stat_client.c
1 /*
2  *------------------------------------------------------------------
3  * stat_client.c - Library for access to VPP statistics segment
4  *
5  * Copyright (c) 2018 Cisco and/or its affiliates.
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at:
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *------------------------------------------------------------------
18  */
19
20 #include <vlib/vlib.h>
21 #include <vppinfra/socket.h>
22 #include <svm/ssvm.h>
23 #include <vpp/stats/stats.h>
24 #include <regex.h>
25 #include "stat_client.h"
26
27 typedef struct
28 {
29   u64 current_epoch;
30   volatile int segment_ready;
31   ssvm_private_t stat_segment;  /* mapped stats segment object */
32   ssvm_shared_header_t *shared_header;
33   clib_spinlock_t *stat_segment_lockp;  /* Spinlock for the stats segment */
34   uword *counter_vector_by_name;
35   u64 *error_base;
36 } stat_client_main_t;
37
38 stat_client_main_t stat_client_main;
39
40 int
41 stat_segment_connect (char *socket_name)
42 {
43   stat_client_main_t *sm = &stat_client_main;
44   ssvm_private_t *ssvmp = &sm->stat_segment;
45   clib_socket_t s = { 0 };
46   clib_error_t *err;
47   int fd = -1, retval;
48
49   memset (sm, 0, sizeof (*sm));
50   s.config = socket_name;
51   s.flags = CLIB_SOCKET_F_IS_CLIENT | CLIB_SOCKET_F_SEQPACKET;
52   err = clib_socket_init (&s);
53   if (err)
54     {
55       clib_error_report (err);
56       return -1;
57     }
58   err = clib_socket_recvmsg (&s, 0, 0, &fd, 1);
59   if (err)
60     {
61       clib_error_report (err);
62       return -1;
63     }
64   clib_socket_close (&s);
65
66   memset (ssvmp, 0, sizeof (*ssvmp));
67   ssvmp->fd = fd;
68
69   /* Note: this closes memfd.fd */
70   retval = ssvm_slave_init_memfd (ssvmp);
71   if (retval)
72     {
73       fprintf (stderr, "WARNING: segment map returned %d\n", retval);
74       return -1;
75     }
76
77   ASSERT (ssvmp && ssvmp->sh);
78
79   /* Pick up the segment lock from the shared memory header */
80   sm->shared_header = ssvmp->sh;
81   sm->stat_segment_lockp = (clib_spinlock_t *) (sm->shared_header->opaque[0]);
82   sm->segment_ready = 1;
83
84   sm->counter_vector_by_name =
85     (uword *) sm->shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR];
86
87   return 0;
88 }
89
90 void
91 stat_segment_disconnect (void)
92 {
93   stat_client_main_t *sm = &stat_client_main;
94   ssvm_delete_memfd (&sm->stat_segment);
95   return;
96 }
97
98 /*
99  * The application needs to register which counters it is interested
100  * in.
101  */
102 stat_segment_cached_pointer_t *
103 stat_segment_register (u8 * stats[])
104 {
105   int i;
106   uword *p;
107   stat_client_main_t *sm = &stat_client_main;
108   stat_segment_cached_pointer_t *cp, *cached_pointer_vec = 0;
109
110   for (i = 0; i < vec_len (stats); i++)
111     {
112       p = hash_get_mem (sm->counter_vector_by_name, stats[i]);
113       if (p == 0)
114         {
115           fprintf (stderr, "WARN: %s not in directory!\n", stats[i]);
116           continue;
117         }
118       vec_add2 (cached_pointer_vec, cp, 1);
119       cp->name = strdup ((char *) stats[i]);    // Point to p->key instead?
120     }
121   return cached_pointer_vec;
122 }
123
124 static u64 *
125 get_error_base (u32 thread_index)
126 {
127   u64 *error_base = 0;
128   uword *p;
129   stat_client_main_t *sm = &stat_client_main;
130   stat_segment_directory_entry_t *ep;
131
132   /* Special case /err/0/counter_vector */
133   p = hash_get_mem (sm->counter_vector_by_name,
134                     format (0, "/err/%d/counter_vector", thread_index));
135   if (p)
136     {
137       ep = (stat_segment_directory_entry_t *) (p[0]);
138       error_base = ep->value;
139     }
140   return error_base;
141 }
142
143 f64
144 stat_segment_heartbeat (void)
145 {
146   f64 *heartbeat = 0;
147   uword *p;
148   stat_client_main_t *sm = &stat_client_main;
149   stat_segment_directory_entry_t *ep;
150
151   /* Special case /err/0/counter_vector */
152   p = hash_get_mem (sm->counter_vector_by_name,
153                     format (0, "/sys/heartbeat%c", 0));
154   if (p)
155     {
156       ep = (stat_segment_directory_entry_t *) (p[0]);
157       heartbeat = ep->value;
158     }
159   return *heartbeat;
160 }
161
162 static void
163 maybe_update_cached_pointers (stat_segment_cached_pointer_t * cached_pointers)
164 {
165   stat_client_main_t *sm = &stat_client_main;
166   stat_segment_cached_pointer_t *cp;
167   uword *p;
168   int i;
169
170   /* Cached pointers OK? */
171   if (sm->current_epoch ==
172       (u64) sm->shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH])
173     return;
174
175   /* Special case /err/0/counter_vector */
176   sm->error_base = get_error_base (0);
177
178   /* Nope, fix them... */
179   for (i = 0; i < vec_len (cached_pointers); i++)
180     {
181       cp = &cached_pointers[i];
182
183       p = hash_get_mem (sm->counter_vector_by_name, cp->name);
184       if (p == 0)
185         {
186           fprintf (stderr, "WARN: %s not in directory!\n", cp->name);
187           continue;
188         }
189       cp->ep = (stat_segment_directory_entry_t *) (p[0]);
190     }
191
192   /* And remember that we did... */
193   sm->current_epoch =
194     (u64) sm->shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH];
195 }
196
197 stat_segment_data_t
198 copy_data (stat_segment_directory_entry_t * ep, u64 * error_base, char *name)
199 {
200   stat_segment_data_t result = { 0 };
201   u32 error_index;
202   int i;
203   vlib_counter_t **combined_c;  /* Combined counter */
204   counter_t **simple_c;         /* Simple counter */
205   result.type = ep->type;
206   result.name = name;
207   switch (ep->type)
208     {
209     case STAT_DIR_TYPE_SCALAR_POINTER:
210       result.scalar_value = *(f64 *) ep->value;
211       break;
212
213     case STAT_DIR_TYPE_VECTOR_POINTER:
214       result.vector_pointer = ep->value;
215       break;
216
217     case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
218       simple_c = ep->value;
219       result.simple_counter_vec = vec_dup (simple_c);
220       for (i = 0; i < vec_len (simple_c); i++)
221         result.simple_counter_vec[i] = vec_dup (simple_c[i]);
222       break;
223
224     case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
225       combined_c = ep->value;
226       result.combined_counter_vec = vec_dup (combined_c);
227       for (i = 0; i < vec_len (combined_c); i++)
228         result.combined_counter_vec[i] = vec_dup (combined_c[i]);
229       break;
230
231     case STAT_DIR_TYPE_ERROR_INDEX:
232       error_index = (uintptr_t) ep->value;
233       result.error_value = error_base[error_index];
234       break;
235
236     default:
237       fprintf (stderr, "Unknown type: %d", ep->type);
238     }
239   return result;
240 }
241
242 stat_segment_data_t *
243 stat_segment_collect (stat_segment_cached_pointer_t * cached_pointers)
244 {
245   stat_client_main_t *sm = &stat_client_main;
246   stat_segment_data_t *res = 0;
247   int i;
248
249   /* Grab the stats segment lock */
250   clib_spinlock_lock (sm->stat_segment_lockp);
251
252   /* see if we need to update cached pointers */
253   maybe_update_cached_pointers (cached_pointers);
254
255   for (i = 0; i < vec_len (cached_pointers); i++)
256     {
257       vec_add1 (res,
258                 copy_data (cached_pointers[i].ep, sm->error_base,
259                            cached_pointers[i].name));
260     }
261
262   /* Drop the lock */
263   clib_spinlock_unlock (sm->stat_segment_lockp);
264
265   return res;
266 }
267
268 void
269 stat_segment_data_free (stat_segment_data_t * res)
270 {
271   int i, j;
272   for (i = 0; i < vec_len (res); i++)
273     {
274       switch (res[i].type)
275         {
276         case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
277           for (j = 0; j < vec_len (res[i].simple_counter_vec); j++)
278             vec_free (res[i].simple_counter_vec[j]);
279           vec_free (res[i].simple_counter_vec);
280           break;
281         case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
282           for (j = 0; j < vec_len (res[i].combined_counter_vec); j++)
283             vec_free (res[i].combined_counter_vec[j]);
284           vec_free (res[i].combined_counter_vec);
285           break;
286         default:
287           ;
288         }
289     }
290   vec_free (res);
291 }
292
293 u8 **
294 stat_segment_ls (u8 ** patterns)
295 {
296   stat_client_main_t *sm = &stat_client_main;
297   hash_pair_t *p;
298   u8 **dir = 0;
299   regex_t regex[vec_len (patterns)];
300
301   int i;
302   for (i = 0; i < vec_len (patterns); i++)
303     {
304       int rv = regcomp (&regex[i], (char *) patterns[i], 0);
305       if (rv)
306         {
307           fprintf (stderr, "Could not compile regex %s\n", patterns[i]);
308           return dir;
309         }
310     }
311
312   clib_spinlock_lock (sm->stat_segment_lockp);
313
314   /* *INDENT-OFF* */
315   hash_foreach_pair (p, sm->counter_vector_by_name,
316   ({
317     for (i = 0; i < vec_len(patterns); i++) {
318       int rv = regexec(&regex[i], (char *)p->key, 0, NULL, 0);
319       if (rv == 0) {
320         vec_add1 (dir, (u8 *)p->key);
321         break;
322       }
323     }
324     if (vec_len(patterns) == 0)
325       vec_add1 (dir, (u8 *)p->key);
326   }));
327   /* *INDENT-ON* */
328
329   clib_spinlock_unlock (sm->stat_segment_lockp);
330
331   for (i = 0; i < vec_len (patterns); i++)
332     regfree (&regex[i]);
333
334   return dir;
335 }
336
337 stat_segment_data_t *
338 stat_segment_dump (u8 * stats[])
339 {
340   int i;
341   uword *p;
342   stat_client_main_t *sm = &stat_client_main;
343   stat_segment_directory_entry_t *ep;
344   stat_segment_data_t *res = 0;
345
346   clib_spinlock_lock (sm->stat_segment_lockp);
347
348   sm->error_base = get_error_base (0);
349   for (i = 0; i < vec_len (stats); i++)
350     {
351       p = hash_get_mem (sm->counter_vector_by_name, stats[i]);
352       if (p == 0)
353         {
354           fprintf (stderr, "WARN: %s not in directory!\n", stats[i]);
355           continue;
356         }
357       /* Collect counter */
358       ep = (stat_segment_directory_entry_t *) (p[0]);
359       vec_add1 (res, copy_data (ep, sm->error_base, (char *) stats[i]));
360     }
361   clib_spinlock_unlock (sm->stat_segment_lockp);
362
363   return res;
364 }
365
366 /* Wrapper for accessing vectors from other languages */
367 int
368 stat_segment_vec_len (void *vec)
369 {
370   return vec_len (vec);
371 }
372
373 /* Create a vector from a string (or add to existing) */
374 u8 **
375 stat_segment_string_vector (u8 ** string_vector, char *string)
376 {
377   u8 *name = 0;
378   name = vec_dup ((u8 *) string);
379   vec_add1 (string_vector, (u8 *) name);
380   return string_vector;
381 }
382
383 /*
384  * fd.io coding-style-patch-verification: ON
385  *
386  * Local Variables:
387  * eval: (c-set-style "gnu")
388  * End:
389  */