mactime: add a "top" command to watch device stats
[vpp.git] / src / plugins / mactime / mactime_top.c
1 #include <vppinfra/time.h>
2 #include <vppinfra/hash.h>
3 #include <vppinfra/pool.h>
4 #include <vpp/stats/stat_segment.h>
5 #include <vpp-api/client/stat_client.h>
6 #include <vppinfra/vec.h>
7 #include <mactime/mactime_device.h>
8 #include <vlibapi/api_common.h>
9 #include <vlibmemory/memory_client.h>
10 #include <vlibmemory/api.h>
11 #include <vnet/api_errno.h>
12 #include <svm/queue.h>
13
14 /* define message IDs */
15 #include <mactime/mactime.api_enum.h>
16 #include <mactime/mactime.api_types.h>
17
18 typedef struct
19 {
20   /* device database */
21   uword *device_by_device_name;
22   mactime_device_t *devices;
23   u32 my_table_epoch;
24
25   /* Stat segment variables */
26   stat_client_main_t *stat_client_main;
27   u8 **pattern1, **pattern2;
28   u32 *ls_result1, *ls_result2;
29   vlib_counter_t *allow_counters;
30   vlib_counter_t *drop_counters;
31
32   /* Timebase */
33   clib_time_t clib_time;
34   clib_timebase_t timebase;
35   f64 timezone_offset;
36   f64 sunday_midnight;
37
38   /* API message-handling */
39   svm_queue_t *vl_input_queue;
40   u32 my_client_index;
41   u16 msg_id_base;
42   volatile u32 result_ready;
43   volatile i32 retval;
44 } mt_main_t;
45
46 mt_main_t mt_main;
47
48 /* Indispensable for debugging in gdb... */
49
50 u32
51 vl (void *x)
52 {
53   return vec_len (x);
54 }
55
56 #define foreach_mactime_api_msg                 \
57 _(MACTIME_DUMP_REPLY, mactime_dump_reply)       \
58 _(MACTIME_DETAILS, mactime_details)
59
60 static void vl_api_mactime_dump_reply_t_handler
61   (vl_api_mactime_dump_reply_t * mp)
62 {
63   mt_main_t *mm = &mt_main;
64   i32 retval = clib_net_to_host_u32 (mp->retval);
65
66   mm->retval = retval;
67   mm->result_ready = 1;
68 }
69
70 static void
71 vl_api_mactime_details_t_handler (vl_api_mactime_details_t * mp)
72 {
73   mt_main_t *mm = &mt_main;
74   mactime_device_t *dev;
75   int i;
76   clib_timebase_range_t *rp;
77   uword *p;
78
79   if (PREDICT_FALSE (mm->device_by_device_name == 0))
80     mm->device_by_device_name = hash_create_string (0, sizeof (uword));
81
82   p = hash_get_mem (mm->device_by_device_name, mp->device_name);
83   if (p)
84     dev = pool_elt_at_index (mm->devices, p[0]);
85   else
86     {
87       u8 *hash_name_copy = format (0, "%s%c", mp->device_name, 0);
88       pool_get (mm->devices, dev);
89       memset (dev, 0, sizeof (*dev));
90       dev->device_name = vec_dup (hash_name_copy);
91       hash_set_mem (mm->device_by_device_name, hash_name_copy,
92                     dev - mm->devices);
93     }
94
95   clib_memcpy_fast (dev->mac_address, mp->mac_address,
96                     sizeof (dev->mac_address));
97   dev->data_quota = clib_net_to_host_u64 (mp->data_quota);
98   dev->data_used_in_range = clib_net_to_host_u64 (mp->data_used_in_range);
99   dev->flags = clib_net_to_host_u32 (mp->flags);
100   dev->pool_index = clib_net_to_host_u32 (mp->pool_index);
101   vec_reset_length (dev->ranges);
102   for (i = 0; i < clib_net_to_host_u32 (mp->nranges); i++)
103     {
104       vec_add2 (dev->ranges, rp, 1);
105       rp->start = mp->ranges[i].start;
106       rp->end = mp->ranges[i].end;
107     }
108 }
109
110 #define vl_print(handle, ...) fformat(handle, __VA_ARGS__)
111
112 #define vl_endianfun
113 #define vl_printfun
114 #define vl_api_version(n,v) static u32 api_version = v;
115 #include <mactime/mactime.api.h>
116 #undef vl_api_version
117 #undef vl_printfun
118 #undef vl_endianfun
119
120 static int
121 connect_to_vpp (char *name)
122 {
123   api_main_t *am = &api_main;
124   mt_main_t *mm = &mt_main;
125   u8 *msg_base_lookup_name;
126
127   if (vl_client_connect_to_vlib ("/vpe-api", name, 32) < 0)
128     return -1;
129
130   mm->vl_input_queue = am->shmem_hdr->vl_input_queue;
131   mm->my_client_index = am->my_client_index;
132
133   msg_base_lookup_name = format (0, "mactime_%08x%c", api_version, 0);
134
135   mm->msg_id_base = vl_client_get_first_plugin_msg_id
136     ((char *) msg_base_lookup_name);
137
138   vec_free (msg_base_lookup_name);
139
140   if (mm->msg_id_base == ~0)
141     return -1;
142
143 #define _(N,n)                                                  \
144     vl_msg_api_set_handlers((VL_API_##N + mm->msg_id_base),     \
145                            #n,                                  \
146                            vl_api_##n##_t_handler,              \
147                            vl_noop_handler,                     \
148                            vl_api_##n##_t_endian,               \
149                            vl_api_##n##_t_print,                \
150                            sizeof(vl_api_##n##_t), 1);
151   foreach_mactime_api_msg;
152 #undef _
153
154   return 0;
155 }
156
157 static void
158 dump_mactime_table (mt_main_t * mm)
159 {
160   vl_api_mactime_dump_t *mp;
161   u32 deadman_counter = 1000;
162
163   /* Send the dump request */
164   mp = vl_msg_api_alloc (sizeof (*mp));
165   memset (mp, 0, sizeof (*mp));
166   mp->_vl_msg_id =
167     clib_host_to_net_u16 (VL_API_MACTIME_DUMP + mm->msg_id_base);
168   mp->client_index = mm->my_client_index;
169   mp->my_table_epoch = mm->my_table_epoch;
170   vl_msg_api_send_shmem (mm->vl_input_queue, (u8 *) & mp);
171
172   /* Wait up to 1 second for vpp to reply */
173   while (deadman_counter-- && mm->result_ready == 0)
174     unix_sleep (1e-3);
175
176   if (mm->retval && (mm->retval != VNET_API_ERROR_NO_CHANGE))
177     clib_warning ("dump reply %d", mm->retval);
178
179 }
180
181 static void
182 scrape_stats_segment (mt_main_t * mm)
183 {
184   vlib_counter_t **counters_by_thread;
185   vlib_counter_t *counters;
186   u64 *offset_vector;
187   mactime_device_t *dev;
188   stat_segment_access_t sa;
189   stat_client_main_t *sm = mm->stat_client_main;
190   stat_segment_directory_entry_t *ep;
191   int need_update2 = 0;
192   static u32 *pool_indices;
193   int i, j;
194
195   vec_reset_length (pool_indices);
196   /* *INDENT-OFF* */
197   pool_foreach (dev, mm->devices,
198   ({
199     vec_add1 (pool_indices, dev->pool_index);
200   }));
201   /* *INDENT-ON* */
202
203   /* Nothing to do... */
204   if (vec_len (pool_indices) == 0)
205     return;
206
207 again1:
208
209   /* Has directory been updated? */
210   if (mm->ls_result1 == 0 || (sm->shared_header->epoch != sm->current_epoch))
211     {
212       need_update2 = 1;
213       vec_free (mm->ls_result1);
214       mm->ls_result1 = stat_segment_ls (mm->pattern1);
215     }
216
217   stat_segment_access_start (&sa, sm);
218
219   ep = vec_elt_at_index (sm->directory_vector, mm->ls_result1[0]);
220   counters_by_thread = stat_segment_pointer (sm->shared_header, ep->offset);
221   offset_vector = stat_segment_pointer (sm->shared_header, ep->offset_vector);
222
223   for (i = 0; i < vec_len (pool_indices); i++)
224     {
225       u32 index = pool_indices[i];
226
227       vec_validate (mm->allow_counters, index);
228       mm->allow_counters[index].packets = 0;
229       mm->allow_counters[index].bytes = 0;
230
231       for (j = 0; j < vec_len (counters_by_thread); j++)
232         {
233           counters = stat_segment_pointer (sm->shared_header,
234                                            offset_vector[j]);
235           mm->allow_counters[index].packets += counters[index].packets;
236           mm->allow_counters[index].bytes += counters[index].bytes;
237         }
238     }
239
240   /* Ugh, segment changed during access. Try again */
241   if (stat_segment_access_end (&sa, sm))
242     goto again1;
243
244   /* Has directory been updated? */
245   if (mm->ls_result2 == 0 || need_update2)
246     {
247       vec_free (mm->ls_result2);
248       mm->ls_result2 = stat_segment_ls (mm->pattern2);
249     }
250
251 again2:
252   stat_segment_access_start (&sa, sm);
253
254   ep = vec_elt_at_index (sm->directory_vector, mm->ls_result2[0]);
255   counters_by_thread = stat_segment_pointer (sm->shared_header, ep->offset);
256   offset_vector = stat_segment_pointer (sm->shared_header, ep->offset_vector);
257
258   for (i = 0; i < vec_len (pool_indices); i++)
259     {
260       u32 index = pool_indices[i];
261
262       vec_validate (mm->drop_counters, index);
263       mm->drop_counters[index].packets = 0;
264       mm->drop_counters[index].bytes = 0;
265
266       for (j = 0; j < vec_len (counters_by_thread); j++)
267         {
268           counters = stat_segment_pointer (sm->shared_header,
269                                            offset_vector[j]);
270           mm->drop_counters[index].packets += counters[index].packets;
271           mm->drop_counters[index].bytes += counters[index].bytes;
272         }
273     }
274   /* Ugh, segment changed during access. Try again */
275   if (stat_segment_access_end (&sa, sm))
276     goto again2;
277 }
278
279 static u8 *
280 format_mac_address (u8 * s, va_list * args)
281 {
282   u8 *a = va_arg (*args, u8 *);
283
284   return format (s, "%02x:%02x:%02x:%02x:%02x:%02x",
285                  a[0], a[1], a[2], a[3], a[4], a[5]);
286 }
287
288 static u8 *
289 format_bytes_with_width (u8 * s, va_list * va)
290 {
291   uword nbytes = va_arg (*va, u64);
292   int width = va_arg (*va, int);
293   f64 nbytes_f64;
294   u8 *fmt;
295   char *suffix = "";
296
297   if (width > 0)
298     fmt = format (0, "%%%d.3f%%s%c", width, 0);
299   else
300     fmt = format (0, "%%.3f%%s%c", 0);
301
302   if (nbytes > (1024ULL * 1024ULL * 1024ULL))
303     {
304       nbytes_f64 = ((f64) nbytes) / (1024.0 * 1024.0 * 1024.0);
305       suffix = "G";
306     }
307   else if (nbytes > (1024ULL * 1024ULL))
308     {
309       nbytes_f64 = ((f64) nbytes) / (1024.0 * 1024.0);
310       suffix = "M";
311     }
312   else if (nbytes > 1024ULL)
313     {
314       nbytes_f64 = ((f64) nbytes) / (1024.0);
315       suffix = "K";
316     }
317   else
318     {
319       nbytes_f64 = (f64) nbytes;
320       suffix = "B";
321     }
322
323   s = format (s, (char *) fmt, nbytes_f64, suffix);
324   vec_free (fmt);
325   return s;
326 }
327
328 static u8 *
329 format_device (u8 * s, va_list * args)
330 {
331   mactime_device_t *dp = va_arg (*args, mactime_device_t *);
332   mt_main_t *mm = &mt_main;
333   int verbose = va_arg (*args, int);
334   int current_status = 99;
335   char *status_string;
336   u8 *macstring = 0;
337   f64 now;
338   int j;
339
340   if (dp == 0)
341     {
342       s = format (s, "%-15s %5s %18s %14s %10s %11s %13s",
343                   "Device Name", "Index", "Addresses", "Status",
344                   "AllowPkt", "AllowByte", "DropPkt");
345       vec_add1 (s, '\n');
346       return s;
347     }
348
349   now = clib_timebase_now (&mm->timebase);
350
351   if (PREDICT_FALSE ((now - mm->sunday_midnight) > 86400.0 * 7.0))
352     mm->sunday_midnight = clib_timebase_find_sunday_midnight (now);
353
354   /* Check dynamic ranges */
355   for (j = 0; j < vec_len (dp->ranges); j++)
356     {
357       clib_timebase_range_t *r = dp->ranges + j;
358       f64 start0, end0;
359
360       start0 = r->start + mm->sunday_midnight;
361       end0 = r->end + mm->sunday_midnight;
362       if (verbose)
363         s = format (s, "  Range %d: %U - %U\n", j,
364                     format_clib_timebase_time, start0,
365                     format_clib_timebase_time, end0);
366
367       if (now >= start0 && now <= end0)
368         {
369           if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW)
370             current_status = 3;
371           else if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA)
372             current_status = 5;
373           else
374             current_status = 2;
375           if (verbose)
376             {
377               s = format (s, "  Time in range %d:", j);
378               s = format (s, "     %U - %U\n",
379                           format_clib_timebase_time, start0,
380                           format_clib_timebase_time, end0);
381             }
382           goto print;
383         }
384     }
385   if (verbose && j)
386     s = format (s, "  No range match.\n");
387   if (dp->flags & MACTIME_DEVICE_FLAG_STATIC_DROP)
388     current_status = 0;
389   if (dp->flags & MACTIME_DEVICE_FLAG_STATIC_ALLOW)
390     current_status = 1;
391   if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW)
392     current_status = 2;
393   if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_DROP)
394     current_status = 3;
395   if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA)
396     current_status = 4;
397
398 print:
399   macstring = format (0, "%U", format_mac_address, dp->mac_address);
400   switch (current_status)
401     {
402     case 0:
403       status_string = "static drop";
404       break;
405     case 1:
406       status_string = "static allow";
407       break;
408     case 2:
409       status_string = "dynamic drop";
410       break;
411     case 3:
412       status_string = "dynamic allow";
413       break;
414     case 4:
415       status_string = "d-quota inact";
416       break;
417     case 5:
418       status_string = "d-quota activ";
419       break;
420     default:
421       status_string = "code bug!";
422       break;
423     }
424
425   s = format (s, "%-15s %5d %18s %14s %10lld %U %13lld\n",
426               dp->device_name, dp->pool_index, macstring, status_string,
427               mm->allow_counters[dp->pool_index].packets,
428               format_bytes_with_width,
429               mm->allow_counters[dp->pool_index].bytes, 10,
430               mm->drop_counters[dp->pool_index].packets);
431   vec_free (macstring);
432
433   if (dp->data_quota > 0)
434     {
435       s = format (s, "%-59s %s%U %s%U", " ", "Quota ",
436                   format_bytes_with_width, dp->data_quota, 10,
437                   "Use ", format_bytes_with_width, dp->data_used_in_range, 8);
438       vec_add1 (s, '\n');
439     }
440   return s;
441 }
442
443 static void
444 print_device_table (mt_main_t * mm)
445 {
446   mactime_device_t *dev;
447
448   fformat (stdout, "%U", format_device, 0 /* header */ , 0 /* verbose */ );
449   /* *INDENT-OFF* */
450   pool_foreach (dev, mm->devices,
451   ({
452     fformat (stdout, "%U", format_device, dev, 0 /* verbose */);
453   }));
454   /* *INDENT-ON* */
455 }
456
457 int
458 main (int argc, char **argv)
459 {
460   mt_main_t *mm = &mt_main;
461   extern stat_client_main_t stat_client_main;
462
463   clib_mem_init (0, 64 << 20);
464
465   if (connect_to_vpp ("mactime_top") < 0)
466     {
467       fformat (stderr, "vpp api client connect error\n");
468       exit (1);
469     }
470
471   if (stat_segment_connect (argv[1]) < 0)
472     {
473       fformat (stderr, "stat segment connect error");
474       exit (1);
475     }
476
477   mm->stat_client_main = (stat_client_main_t *) & stat_client_main;
478
479   /* US EDT - $$$ FIXME */
480   clib_time_init (&mm->clib_time);
481   mm->timezone_offset = -5.0;
482   clib_timebase_init (&mm->timebase, mm->timezone_offset,
483                       CLIB_TIMEBASE_DAYLIGHT_USA);
484
485   vec_add1 (mm->pattern1, (u8 *) "^/mactime/allow");
486   vec_add1 (mm->pattern2, (u8 *) "^/mactime/drop");
487
488   while (1)
489     {
490       dump_mactime_table (mm);
491       scrape_stats_segment (mm);
492       print_device_table (mm);
493       unix_sleep (5.0);
494     }
495   return 0;
496 }
497
498
499 /*
500  * fd.io coding-style-patch-verification: ON
501  *
502  * Local Variables:
503  * eval: (c-set-style "gnu")
504  * End:
505  */