5e9b0d6911e934727c6dfc8fac3666172f94a87b
[vpp.git] / src / vpp / stats / stats.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <vpp/stats/stats.h>
16 #include <signal.h>
17 #include <vlib/threads.h>
18 #include <vnet/fib/fib_entry.h>
19 #include <vnet/fib/fib_table.h>
20 #include <vnet/dpo/load_balance.h>
21
22 #define STATS_DEBUG 0
23
24 stats_main_t stats_main;
25
26 #include <vnet/ip/ip.h>
27
28 #include <vpp/api/vpe_msg_enum.h>
29
30 #define f64_endian(a)
31 #define f64_print(a,b)
32
33 #define vl_typedefs             /* define message structures */
34 #include <vpp/api/vpe_all_api_h.h>
35 #undef vl_typedefs
36
37 #define vl_endianfun            /* define message structures */
38 #include <vpp/api/vpe_all_api_h.h>
39 #undef vl_endianfun
40
41 /* instantiate all the print functions we know about */
42 #define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
43 #define vl_printfun
44 #include <vpp/api/vpe_all_api_h.h>
45 #undef vl_printfun
46
47 #define foreach_stats_msg                               \
48 _(WANT_STATS, want_stats)                               \
49 _(WANT_STATS_REPLY, want_stats_reply)                   \
50 _(VNET_INTERFACE_COUNTERS, vnet_interface_counters)     \
51 _(VNET_IP4_FIB_COUNTERS, vnet_ip4_fib_counters)         \
52 _(VNET_IP6_FIB_COUNTERS, vnet_ip6_fib_counters)         \
53 _(VNET_IP4_NBR_COUNTERS, vnet_ip4_nbr_counters)         \
54 _(VNET_IP6_NBR_COUNTERS, vnet_ip6_nbr_counters)
55
56 /* These constants ensure msg sizes <= 1024, aka ring allocation */
57 #define SIMPLE_COUNTER_BATCH_SIZE       126
58 #define COMBINED_COUNTER_BATCH_SIZE     63
59 #define IP4_FIB_COUNTER_BATCH_SIZE      48
60 #define IP6_FIB_COUNTER_BATCH_SIZE      30
61
62 /* 5ms */
63 #define STATS_RELEASE_DELAY_NS (1000 * 1000 * 5)
64 /*                              ns/us  us/ms        */
65
66 void
67 dslock (stats_main_t * sm, int release_hint, int tag)
68 {
69   u32 thread_id;
70   data_structure_lock_t *l = sm->data_structure_lock;
71
72   if (PREDICT_FALSE (l == 0))
73     return;
74
75   thread_id = os_get_cpu_number ();
76   if (l->lock && l->thread_id == thread_id)
77     {
78       l->count++;
79       return;
80     }
81
82   if (release_hint)
83     l->release_hint++;
84
85   while (__sync_lock_test_and_set (&l->lock, 1))
86     /* zzzz */ ;
87   l->tag = tag;
88   l->thread_id = thread_id;
89   l->count = 1;
90 }
91
92 void
93 stats_dslock_with_hint (int hint, int tag)
94 {
95   stats_main_t *sm = &stats_main;
96   dslock (sm, hint, tag);
97 }
98
99 void
100 dsunlock (stats_main_t * sm)
101 {
102   u32 thread_id;
103   data_structure_lock_t *l = sm->data_structure_lock;
104
105   if (PREDICT_FALSE (l == 0))
106     return;
107
108   thread_id = os_get_cpu_number ();
109   ASSERT (l->lock && l->thread_id == thread_id);
110   l->count--;
111   if (l->count == 0)
112     {
113       l->tag = -l->tag;
114       l->release_hint = 0;
115       CLIB_MEMORY_BARRIER ();
116       l->lock = 0;
117     }
118 }
119
120 void
121 stats_dsunlock (int hint, int tag)
122 {
123   stats_main_t *sm = &stats_main;
124   dsunlock (sm);
125 }
126
127 static void
128 do_simple_interface_counters (stats_main_t * sm)
129 {
130   vl_api_vnet_interface_counters_t *mp = 0;
131   vnet_interface_main_t *im = sm->interface_main;
132   api_main_t *am = sm->api_main;
133   vl_shmem_hdr_t *shmem_hdr = am->shmem_hdr;
134   unix_shared_memory_queue_t *q = shmem_hdr->vl_input_queue;
135   vlib_simple_counter_main_t *cm;
136   u32 items_this_message = 0;
137   u64 v, *vp = 0;
138   int i;
139
140   /*
141    * Prevent interface registration from expanding / moving the vectors...
142    * That tends never to happen, so we can hold this lock for a while.
143    */
144   vnet_interface_counter_lock (im);
145
146   vec_foreach (cm, im->sw_if_counters)
147   {
148
149     for (i = 0; i < vec_len (cm->maxi); i++)
150       {
151         if (mp == 0)
152           {
153             items_this_message = clib_min (SIMPLE_COUNTER_BATCH_SIZE,
154                                            vec_len (cm->maxi) - i);
155
156             mp = vl_msg_api_alloc_as_if_client
157               (sizeof (*mp) + items_this_message * sizeof (v));
158             mp->_vl_msg_id = ntohs (VL_API_VNET_INTERFACE_COUNTERS);
159             mp->vnet_counter_type = cm - im->sw_if_counters;
160             mp->is_combined = 0;
161             mp->first_sw_if_index = htonl (i);
162             mp->count = 0;
163             vp = (u64 *) mp->data;
164           }
165         v = vlib_get_simple_counter (cm, i);
166         clib_mem_unaligned (vp, u64) = clib_host_to_net_u64 (v);
167         vp++;
168         mp->count++;
169         if (mp->count == items_this_message)
170           {
171             mp->count = htonl (items_this_message);
172             /* Send to the main thread... */
173             vl_msg_api_send_shmem (q, (u8 *) & mp);
174             mp = 0;
175           }
176       }
177     ASSERT (mp == 0);
178   }
179   vnet_interface_counter_unlock (im);
180 }
181
182 static void
183 do_combined_interface_counters (stats_main_t * sm)
184 {
185   vl_api_vnet_interface_counters_t *mp = 0;
186   vnet_interface_main_t *im = sm->interface_main;
187   api_main_t *am = sm->api_main;
188   vl_shmem_hdr_t *shmem_hdr = am->shmem_hdr;
189   unix_shared_memory_queue_t *q = shmem_hdr->vl_input_queue;
190   vlib_combined_counter_main_t *cm;
191   u32 items_this_message = 0;
192   vlib_counter_t v, *vp = 0;
193   int i;
194
195   vnet_interface_counter_lock (im);
196
197   vec_foreach (cm, im->combined_sw_if_counters)
198   {
199
200     for (i = 0; i < vec_len (cm->maxi); i++)
201       {
202         if (mp == 0)
203           {
204             items_this_message = clib_min (COMBINED_COUNTER_BATCH_SIZE,
205                                            vec_len (cm->maxi) - i);
206
207             mp = vl_msg_api_alloc_as_if_client
208               (sizeof (*mp) + items_this_message * sizeof (v));
209             mp->_vl_msg_id = ntohs (VL_API_VNET_INTERFACE_COUNTERS);
210             mp->vnet_counter_type = cm - im->combined_sw_if_counters;
211             mp->is_combined = 1;
212             mp->first_sw_if_index = htonl (i);
213             mp->count = 0;
214             vp = (vlib_counter_t *) mp->data;
215           }
216         vlib_get_combined_counter (cm, i, &v);
217         clib_mem_unaligned (&vp->packets, u64)
218           = clib_host_to_net_u64 (v.packets);
219         clib_mem_unaligned (&vp->bytes, u64) = clib_host_to_net_u64 (v.bytes);
220         vp++;
221         mp->count++;
222         if (mp->count == items_this_message)
223           {
224             mp->count = htonl (items_this_message);
225             /* Send to the main thread... */
226             vl_msg_api_send_shmem (q, (u8 *) & mp);
227             mp = 0;
228           }
229       }
230     ASSERT (mp == 0);
231   }
232   vnet_interface_counter_unlock (im);
233 }
234
235 /* from .../vnet/vnet/ip/lookup.c. Yuck */
236 typedef CLIB_PACKED (struct
237                      {
238                      ip4_address_t address;
239 u32 address_length: 6;
240 u32 index:           26;
241                      }) ip4_route_t;
242
243 static void
244 ip46_fib_stats_delay (stats_main_t * sm, u32 sec, u32 nsec)
245 {
246   struct timespec _req, *req = &_req;
247   struct timespec _rem, *rem = &_rem;
248
249   req->tv_sec = sec;
250   req->tv_nsec = nsec;
251   while (1)
252     {
253       if (nanosleep (req, rem) == 0)
254         break;
255       *req = *rem;
256       if (errno == EINTR)
257         continue;
258       clib_unix_warning ("nanosleep");
259       break;
260     }
261 }
262
263 /**
264  * @brief The context passed when collecting adjacency counters
265  */
266 typedef struct ip4_nbr_stats_ctx_t_
267 {
268   /**
269    * The SW IF index all these adjs belong to
270    */
271   u32 sw_if_index;
272
273   /**
274    * A vector of ip4 nbr counters
275    */
276   vl_api_ip4_nbr_counter_t *counters;
277 } ip4_nbr_stats_ctx_t;
278
279 static adj_walk_rc_t
280 ip4_nbr_stats_cb (adj_index_t ai, void *arg)
281 {
282   vl_api_ip4_nbr_counter_t *vl_counter;
283   vlib_counter_t adj_counter;
284   ip4_nbr_stats_ctx_t *ctx;
285   ip_adjacency_t *adj;
286
287   ctx = arg;
288   vlib_get_combined_counter (&adjacency_counters, ai, &adj_counter);
289
290   if (0 != adj_counter.packets)
291     {
292       vec_add2 (ctx->counters, vl_counter, 1);
293       adj = adj_get (ai);
294
295       vl_counter->packets = clib_host_to_net_u64 (adj_counter.packets);
296       vl_counter->bytes = clib_host_to_net_u64 (adj_counter.bytes);
297       vl_counter->address = adj->sub_type.nbr.next_hop.ip4.as_u32;
298       vl_counter->link_type = adj->ia_link;
299     }
300   return (ADJ_WALK_RC_CONTINUE);
301 }
302
303 #define MIN(x,y) (((x)<(y))?(x):(y))
304
305 static void
306 ip4_nbr_ship (stats_main_t * sm, ip4_nbr_stats_ctx_t * ctx)
307 {
308   api_main_t *am = sm->api_main;
309   vl_shmem_hdr_t *shmem_hdr = am->shmem_hdr;
310   unix_shared_memory_queue_t *q = shmem_hdr->vl_input_queue;
311   vl_api_vnet_ip4_nbr_counters_t *mp = 0;
312   int first = 0;
313
314   /*
315    * If the walk context has counters, which may be left over from the last
316    * suspend, then we continue from there.
317    */
318   while (0 != vec_len (ctx->counters))
319     {
320       u32 n_items = MIN (vec_len (ctx->counters),
321                          IP4_FIB_COUNTER_BATCH_SIZE);
322       u8 pause = 0;
323
324       dslock (sm, 0 /* release hint */ , 1 /* tag */ );
325
326       mp = vl_msg_api_alloc_as_if_client (sizeof (*mp) +
327                                           (n_items *
328                                            sizeof
329                                            (vl_api_ip4_nbr_counter_t)));
330       mp->_vl_msg_id = ntohs (VL_API_VNET_IP4_NBR_COUNTERS);
331       mp->count = ntohl (n_items);
332       mp->sw_if_index = ntohl (ctx->sw_if_index);
333       mp->begin = first;
334       first = 0;
335
336       /*
337        * copy the counters from the back of the context, then we can easily
338        * 'erase' them by resetting the vector length.
339        * The order we push the stats to the caller is not important.
340        */
341       clib_memcpy (mp->c,
342                    &ctx->counters[vec_len (ctx->counters) - n_items],
343                    n_items * sizeof (*ctx->counters));
344
345       _vec_len (ctx->counters) = vec_len (ctx->counters) - n_items;
346
347       /*
348        * send to the shm q
349        */
350       unix_shared_memory_queue_lock (q);
351       pause = unix_shared_memory_queue_is_full (q);
352
353       vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
354       unix_shared_memory_queue_unlock (q);
355       dsunlock (sm);
356
357       if (pause)
358         ip46_fib_stats_delay (sm, 0 /* sec */ ,
359                               STATS_RELEASE_DELAY_NS);
360     }
361 }
362
363 static void
364 do_ip4_nbrs (stats_main_t * sm)
365 {
366   vnet_main_t *vnm = vnet_get_main ();
367   vnet_interface_main_t *im = &vnm->interface_main;
368   vnet_sw_interface_t *si;
369
370   ip4_nbr_stats_ctx_t ctx = {
371     .sw_if_index = 0,
372     .counters = NULL,
373   };
374
375   /* *INDENT-OFF* */
376   pool_foreach (si, im->sw_interfaces,
377   ({
378     /*
379      * update the interface we are now concerned with
380      */
381     ctx.sw_if_index = si->sw_if_index;
382
383     /*
384      * we are about to walk another interface, so we shouldn't have any pending
385      * stats to export.
386      */
387     ASSERT(ctx.counters == NULL);
388
389     /*
390      * visit each neighbour adjacency on the interface and collect
391      * its current stats.
392      * Because we hold the lock the walk is synchronous, so safe to routing
393      * updates. It's limited in work by the number of adjacenies on an
394      * interface, which is typically not huge.
395      */
396     dslock (sm, 0 /* release hint */ , 1 /* tag */ );
397     adj_nbr_walk (si->sw_if_index,
398                   FIB_PROTOCOL_IP4,
399                   ip4_nbr_stats_cb,
400                   &ctx);
401     dsunlock (sm);
402
403     /*
404      * if this interface has some adjacencies with counters then ship them,
405      * else continue to the next interface.
406      */
407     if (NULL != ctx.counters)
408       {
409         ip4_nbr_ship(sm, &ctx);
410       }
411   }));
412   /* *INDENT-OFF* */
413 }
414
415 /**
416  * @brief The context passed when collecting adjacency counters
417  */
418 typedef struct ip6_nbr_stats_ctx_t_
419 {
420   /**
421    * The SW IF index all these adjs belong to
422    */
423   u32 sw_if_index;
424
425   /**
426    * A vector of ip6 nbr counters
427    */
428   vl_api_ip6_nbr_counter_t *counters;
429 } ip6_nbr_stats_ctx_t;
430
431 static adj_walk_rc_t
432 ip6_nbr_stats_cb (adj_index_t ai,
433                   void *arg)
434 {
435   vl_api_ip6_nbr_counter_t *vl_counter;
436   vlib_counter_t adj_counter;
437   ip6_nbr_stats_ctx_t *ctx;
438   ip_adjacency_t *adj;
439
440   ctx = arg;
441   vlib_get_combined_counter(&adjacency_counters, ai, &adj_counter);
442
443   if (0 != adj_counter.packets)
444     {
445       vec_add2(ctx->counters, vl_counter, 1);
446       adj = adj_get(ai);
447
448       vl_counter->packets = clib_host_to_net_u64(adj_counter.packets);
449       vl_counter->bytes   = clib_host_to_net_u64(adj_counter.bytes);
450       vl_counter->address[0] = adj->sub_type.nbr.next_hop.ip6.as_u64[0];
451       vl_counter->address[1] = adj->sub_type.nbr.next_hop.ip6.as_u64[1];
452       vl_counter->link_type = adj->ia_link;
453     }
454   return (ADJ_WALK_RC_CONTINUE);
455 }
456
457 #define MIN(x,y) (((x)<(y))?(x):(y))
458
459 static void
460 ip6_nbr_ship (stats_main_t * sm,
461               ip6_nbr_stats_ctx_t *ctx)
462 {
463   api_main_t *am = sm->api_main;
464   vl_shmem_hdr_t *shmem_hdr = am->shmem_hdr;
465   unix_shared_memory_queue_t *q = shmem_hdr->vl_input_queue;
466   vl_api_vnet_ip6_nbr_counters_t *mp = 0;
467   int first = 0;
468
469   /*
470    * If the walk context has counters, which may be left over from the last
471    * suspend, then we continue from there.
472    */
473   while (0 != vec_len(ctx->counters))
474     {
475       u32 n_items = MIN (vec_len (ctx->counters),
476                          IP6_FIB_COUNTER_BATCH_SIZE);
477       u8 pause = 0;
478
479       dslock (sm, 0 /* release hint */ , 1 /* tag */ );
480
481       mp = vl_msg_api_alloc_as_if_client (sizeof (*mp) +
482                                           (n_items *
483                                            sizeof
484                                            (vl_api_ip6_nbr_counter_t)));
485       mp->_vl_msg_id = ntohs (VL_API_VNET_IP6_NBR_COUNTERS);
486       mp->count = ntohl (n_items);
487       mp->sw_if_index = ntohl (ctx->sw_if_index);
488       mp->begin = first;
489       first = 0;
490
491       /*
492        * copy the counters from the back of the context, then we can easily
493        * 'erase' them by resetting the vector length.
494        * The order we push the stats to the caller is not important.
495        */
496       clib_memcpy (mp->c,
497                    &ctx->counters[vec_len (ctx->counters) - n_items],
498                    n_items * sizeof (*ctx->counters));
499
500       _vec_len (ctx->counters) = vec_len (ctx->counters) - n_items;
501
502       /*
503        * send to the shm q
504        */
505       unix_shared_memory_queue_lock (q);
506       pause = unix_shared_memory_queue_is_full (q);
507
508       vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
509       unix_shared_memory_queue_unlock (q);
510       dsunlock (sm);
511
512       if (pause)
513         ip46_fib_stats_delay (sm, 0 /* sec */ ,
514                               STATS_RELEASE_DELAY_NS);
515     }
516 }
517
518 static void
519 do_ip6_nbrs (stats_main_t * sm)
520 {
521   vnet_main_t *vnm = vnet_get_main ();
522   vnet_interface_main_t *im = &vnm->interface_main;
523   vnet_sw_interface_t *si;
524
525   ip6_nbr_stats_ctx_t ctx = {
526     .sw_if_index = 0,
527     .counters = NULL,
528   };
529
530   /* *INDENT-OFF* */
531   pool_foreach (si, im->sw_interfaces,
532   ({
533     /*
534      * update the interface we are now concerned with
535      */
536     ctx.sw_if_index = si->sw_if_index;
537
538     /*
539      * we are about to walk another interface, so we shouldn't have any pending
540      * stats to export.
541      */
542     ASSERT(ctx.counters == NULL);
543
544     /*
545      * visit each neighbour adjacency on the interface and collect
546      * its current stats.
547      * Because we hold the lock the walk is synchronous, so safe to routing
548      * updates. It's limited in work by the number of adjacenies on an
549      * interface, which is typically not huge.
550      */
551     dslock (sm, 0 /* release hint */ , 1 /* tag */ );
552     adj_nbr_walk (si->sw_if_index,
553                   FIB_PROTOCOL_IP6,
554                   ip6_nbr_stats_cb,
555                   &ctx);
556     dsunlock (sm);
557
558     /*
559      * if this interface has some adjacencies with counters then ship them,
560      * else continue to the next interface.
561      */
562     if (NULL != ctx.counters)
563       {
564         ip6_nbr_ship(sm, &ctx);
565       }
566   }));
567   /* *INDENT-OFF* */
568 }
569
570 static void
571 do_ip4_fibs (stats_main_t * sm)
572 {
573   ip4_main_t *im4 = &ip4_main;
574   api_main_t *am = sm->api_main;
575   vl_shmem_hdr_t *shmem_hdr = am->shmem_hdr;
576   unix_shared_memory_queue_t *q = shmem_hdr->vl_input_queue;
577   static ip4_route_t *routes;
578   ip4_route_t *r;
579   fib_table_t *fib;
580   ip_lookup_main_t *lm = &im4->lookup_main;
581   static uword *results;
582   vl_api_vnet_ip4_fib_counters_t *mp = 0;
583   u32 items_this_message;
584   vl_api_ip4_fib_counter_t *ctrp = 0;
585   u32 start_at_fib_index = 0;
586   int i;
587
588 again:
589   /* *INDENT-OFF* */
590   pool_foreach (fib, im4->fibs,
591   ({
592     /* We may have bailed out due to control-plane activity */
593     while ((fib - im4->fibs) < start_at_fib_index)
594       continue;
595
596     if (mp == 0)
597       {
598         items_this_message = IP4_FIB_COUNTER_BATCH_SIZE;
599         mp = vl_msg_api_alloc_as_if_client
600           (sizeof (*mp) +
601            items_this_message * sizeof (vl_api_ip4_fib_counter_t));
602         mp->_vl_msg_id = ntohs (VL_API_VNET_IP4_FIB_COUNTERS);
603         mp->count = 0;
604         mp->vrf_id = ntohl (fib->ft_table_id);
605         ctrp = (vl_api_ip4_fib_counter_t *) mp->c;
606       }
607     else
608       {
609         /* happens if the last FIB was empty... */
610         ASSERT (mp->count == 0);
611         mp->vrf_id = ntohl (fib->ft_table_id);
612       }
613
614     dslock (sm, 0 /* release hint */ , 1 /* tag */ );
615
616     vec_reset_length (routes);
617     vec_reset_length (results);
618
619     for (i = 0; i < ARRAY_LEN (fib->v4.fib_entry_by_dst_address); i++)
620       {
621         uword *hash = fib->v4.fib_entry_by_dst_address[i];
622         hash_pair_t *p;
623         ip4_route_t x;
624
625         x.address_length = i;
626
627         hash_foreach_pair (p, hash,
628         ({
629           x.address.data_u32 = p->key;
630           x.index = p->value[0];
631
632           vec_add1 (routes, x);
633           if (sm->data_structure_lock->release_hint)
634             {
635               start_at_fib_index = fib - im4->fibs;
636               dsunlock (sm);
637               ip46_fib_stats_delay (sm, 0 /* sec */,
638                                     STATS_RELEASE_DELAY_NS);
639               mp->count = 0;
640               ctrp = (vl_api_ip4_fib_counter_t *)mp->c;
641               goto again;
642             }
643         }));
644       }
645
646     vec_foreach (r, routes)
647       {
648         vlib_counter_t c;
649
650         vlib_get_combined_counter (&load_balance_main.lbm_to_counters,
651                                    r->index, &c);
652         /*
653          * If it has actually
654          * seen at least one packet, send it.
655          */
656         if (c.packets > 0)
657           {
658
659             /* already in net byte order */
660             ctrp->address = r->address.as_u32;
661             ctrp->address_length = r->address_length;
662             ctrp->packets = clib_host_to_net_u64 (c.packets);
663             ctrp->bytes = clib_host_to_net_u64 (c.bytes);
664             mp->count++;
665             ctrp++;
666
667             if (mp->count == items_this_message)
668               {
669                 mp->count = htonl (items_this_message);
670                 /*
671                  * If the main thread's input queue is stuffed,
672                  * drop the data structure lock (which the main thread
673                  * may want), and take a pause.
674                  */
675                 unix_shared_memory_queue_lock (q);
676                 if (unix_shared_memory_queue_is_full (q))
677                   {
678                     dsunlock (sm);
679                     vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
680                     unix_shared_memory_queue_unlock (q);
681                     mp = 0;
682                     ip46_fib_stats_delay (sm, 0 /* sec */ ,
683                                           STATS_RELEASE_DELAY_NS);
684                     goto again;
685                   }
686                 vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
687                 unix_shared_memory_queue_unlock (q);
688
689                 items_this_message = IP4_FIB_COUNTER_BATCH_SIZE;
690                 mp = vl_msg_api_alloc_as_if_client
691                   (sizeof (*mp) +
692                    items_this_message * sizeof (vl_api_ip4_fib_counter_t));
693                 mp->_vl_msg_id = ntohs (VL_API_VNET_IP4_FIB_COUNTERS);
694                 mp->count = 0;
695                 mp->vrf_id = ntohl (fib->ft_table_id);
696                 ctrp = (vl_api_ip4_fib_counter_t *) mp->c;
697               }
698           }                     /* for each (mp or single) adj */
699         if (sm->data_structure_lock->release_hint)
700           {
701             start_at_fib_index = fib - im4->fibs;
702             dsunlock (sm);
703             ip46_fib_stats_delay (sm, 0 /* sec */ , STATS_RELEASE_DELAY_NS);
704             mp->count = 0;
705             ctrp = (vl_api_ip4_fib_counter_t *) mp->c;
706             goto again;
707           }
708       }                         /* vec_foreach (routes) */
709
710     dsunlock (sm);
711
712     /* Flush any data from this fib */
713     if (mp->count)
714       {
715         mp->count = htonl (mp->count);
716         vl_msg_api_send_shmem (q, (u8 *) & mp);
717         mp = 0;
718       }
719   }));
720   /* *INDENT-ON* */
721
722   /* If e.g. the last FIB had no reportable routes, free the buffer */
723   if (mp)
724     vl_msg_api_free (mp);
725 }
726
727 typedef struct
728 {
729   ip6_address_t address;
730   u32 address_length;
731   u32 index;
732 } ip6_route_t;
733
734 typedef struct
735 {
736   u32 fib_index;
737   ip6_route_t **routep;
738   stats_main_t *sm;
739 } add_routes_in_fib_arg_t;
740
741 static void
742 add_routes_in_fib (BVT (clib_bihash_kv) * kvp, void *arg)
743 {
744   add_routes_in_fib_arg_t *ap = arg;
745   stats_main_t *sm = ap->sm;
746
747   if (sm->data_structure_lock->release_hint)
748     clib_longjmp (&sm->jmp_buf, 1);
749
750   if (kvp->key[2] >> 32 == ap->fib_index)
751     {
752       ip6_address_t *addr;
753       ip6_route_t *r;
754       addr = (ip6_address_t *) kvp;
755       vec_add2 (*ap->routep, r, 1);
756       r->address = addr[0];
757       r->address_length = kvp->key[2] & 0xFF;
758       r->index = kvp->value;
759     }
760 }
761
762 static void
763 do_ip6_fibs (stats_main_t * sm)
764 {
765   ip6_main_t *im6 = &ip6_main;
766   api_main_t *am = sm->api_main;
767   vl_shmem_hdr_t *shmem_hdr = am->shmem_hdr;
768   unix_shared_memory_queue_t *q = shmem_hdr->vl_input_queue;
769   static ip6_route_t *routes;
770   ip6_route_t *r;
771   fib_table_t *fib;
772   static uword *results;
773   vl_api_vnet_ip6_fib_counters_t *mp = 0;
774   u32 items_this_message;
775   vl_api_ip6_fib_counter_t *ctrp = 0;
776   u32 start_at_fib_index = 0;
777   BVT (clib_bihash) * h = &im6->ip6_table[IP6_FIB_TABLE_FWDING].ip6_hash;
778   add_routes_in_fib_arg_t _a, *a = &_a;
779
780 again:
781   /* *INDENT-OFF* */
782   pool_foreach (fib, im6->fibs,
783   ({
784     /* We may have bailed out due to control-plane activity */
785     while ((fib - im6->fibs) < start_at_fib_index)
786       continue;
787
788     if (mp == 0)
789       {
790         items_this_message = IP6_FIB_COUNTER_BATCH_SIZE;
791         mp = vl_msg_api_alloc_as_if_client
792           (sizeof (*mp) +
793            items_this_message * sizeof (vl_api_ip6_fib_counter_t));
794         mp->_vl_msg_id = ntohs (VL_API_VNET_IP6_FIB_COUNTERS);
795         mp->count = 0;
796         mp->vrf_id = ntohl (fib->ft_table_id);
797         ctrp = (vl_api_ip6_fib_counter_t *) mp->c;
798       }
799
800     dslock (sm, 0 /* release hint */ , 1 /* tag */ );
801
802     vec_reset_length (routes);
803     vec_reset_length (results);
804
805     a->fib_index = fib - im6->fibs;
806     a->routep = &routes;
807     a->sm = sm;
808
809     if (clib_setjmp (&sm->jmp_buf, 0) == 0)
810       {
811         start_at_fib_index = fib - im6->fibs;
812         BV (clib_bihash_foreach_key_value_pair) (h, add_routes_in_fib, a);
813       }
814     else
815       {
816         dsunlock (sm);
817         ip46_fib_stats_delay (sm, 0 /* sec */ ,
818                               STATS_RELEASE_DELAY_NS);
819         mp->count = 0;
820         ctrp = (vl_api_ip6_fib_counter_t *) mp->c;
821         goto again;
822       }
823
824     vec_foreach (r, routes)
825     {
826         vlib_counter_t c;
827
828         vlib_get_combined_counter (&load_balance_main.lbm_to_counters,
829                                    r->index, &c);
830         /*
831          * If it has actually
832          * seen at least one packet, send it.
833          */
834         if (c.packets > 0)
835           {
836             /* already in net byte order */
837             ctrp->address[0] = r->address.as_u64[0];
838             ctrp->address[1] = r->address.as_u64[1];
839             ctrp->address_length = (u8) r->address_length;
840             ctrp->packets = clib_host_to_net_u64 (c.packets);
841             ctrp->bytes = clib_host_to_net_u64 (c.bytes);
842             mp->count++;
843             ctrp++;
844
845             if (mp->count == items_this_message)
846               {
847                 mp->count = htonl (items_this_message);
848                 /*
849                  * If the main thread's input queue is stuffed,
850                  * drop the data structure lock (which the main thread
851                  * may want), and take a pause.
852                  */
853                 unix_shared_memory_queue_lock (q);
854                 if (unix_shared_memory_queue_is_full (q))
855                   {
856                     dsunlock (sm);
857                     vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
858                     unix_shared_memory_queue_unlock (q);
859                     mp = 0;
860                     ip46_fib_stats_delay (sm, 0 /* sec */ ,
861                                           STATS_RELEASE_DELAY_NS);
862                     goto again;
863                   }
864                 vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
865                 unix_shared_memory_queue_unlock (q);
866
867                 items_this_message = IP6_FIB_COUNTER_BATCH_SIZE;
868                 mp = vl_msg_api_alloc_as_if_client
869                   (sizeof (*mp) +
870                    items_this_message * sizeof (vl_api_ip6_fib_counter_t));
871                 mp->_vl_msg_id = ntohs (VL_API_VNET_IP6_FIB_COUNTERS);
872                 mp->count = 0;
873                 mp->vrf_id = ntohl (fib->ft_table_id);
874                 ctrp = (vl_api_ip6_fib_counter_t *) mp->c;
875               }
876           }
877
878         if (sm->data_structure_lock->release_hint)
879           {
880             start_at_fib_index = fib - im6->fibs;
881             dsunlock (sm);
882             ip46_fib_stats_delay (sm, 0 /* sec */ , STATS_RELEASE_DELAY_NS);
883             mp->count = 0;
884             ctrp = (vl_api_ip6_fib_counter_t *) mp->c;
885             goto again;
886           }
887     }                           /* vec_foreach (routes) */
888
889     dsunlock (sm);
890
891     /* Flush any data from this fib */
892     if (mp->count)
893       {
894         mp->count = htonl (mp->count);
895         vl_msg_api_send_shmem (q, (u8 *) & mp);
896         mp = 0;
897       }
898   }));
899   /* *INDENT-ON* */
900
901   /* If e.g. the last FIB had no reportable routes, free the buffer */
902   if (mp)
903     vl_msg_api_free (mp);
904 }
905
906 static void
907 stats_thread_fn (void *arg)
908 {
909   stats_main_t *sm = &stats_main;
910   vlib_worker_thread_t *w = (vlib_worker_thread_t *) arg;
911   vlib_thread_main_t *tm = vlib_get_thread_main ();
912
913   /* stats thread wants no signals. */
914   {
915     sigset_t s;
916     sigfillset (&s);
917     pthread_sigmask (SIG_SETMASK, &s, 0);
918   }
919
920   if (vec_len (tm->thread_prefix))
921     vlib_set_thread_name ((char *)
922                           format (0, "%v_stats%c", tm->thread_prefix, '\0'));
923
924   clib_mem_set_heap (w->thread_mheap);
925
926   while (1)
927     {
928       /* 10 second poll interval */
929       ip46_fib_stats_delay (sm, 10 /* secs */ , 0 /* nsec */ );
930
931       if (!(sm->enable_poller))
932         continue;
933       do_simple_interface_counters (sm);
934       do_combined_interface_counters (sm);
935       do_ip4_fibs (sm);
936       do_ip6_fibs (sm);
937       do_ip4_nbrs (sm);
938       do_ip6_nbrs (sm);
939     }
940 }
941
942 static void
943 vl_api_vnet_interface_counters_t_handler (vl_api_vnet_interface_counters_t *
944                                           mp)
945 {
946   vpe_client_registration_t *reg;
947   stats_main_t *sm = &stats_main;
948   unix_shared_memory_queue_t *q, *q_prev = NULL;
949   vl_api_vnet_interface_counters_t *mp_copy = NULL;
950   u32 mp_size;
951
952 #if STATS_DEBUG > 0
953   char *counter_name;
954   u32 count, sw_if_index;
955   int i;
956 #endif
957
958   mp_size = sizeof (*mp) + (ntohl (mp->count) *
959                             (mp->is_combined ? sizeof (vlib_counter_t) :
960                              sizeof (u64)));
961
962   /* *INDENT-OFF* */
963   pool_foreach(reg, sm->stats_registrations,
964   ({
965     q = vl_api_client_index_to_input_queue (reg->client_index);
966     if (q)
967       {
968         if (q_prev && (q_prev->cursize < q_prev->maxsize))
969           {
970             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
971             clib_memcpy(mp_copy, mp, mp_size);
972             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
973             mp = mp_copy;
974           }
975         q_prev = q;
976       }
977   }));
978   /* *INDENT-ON* */
979
980 #if STATS_DEBUG > 0
981   count = ntohl (mp->count);
982   sw_if_index = ntohl (mp->first_sw_if_index);
983   if (mp->is_combined == 0)
984     {
985       u64 *vp, v;
986       vp = (u64 *) mp->data;
987
988       switch (mp->vnet_counter_type)
989         {
990         case VNET_INTERFACE_COUNTER_DROP:
991           counter_name = "drop";
992           break;
993         case VNET_INTERFACE_COUNTER_PUNT:
994           counter_name = "punt";
995           break;
996         case VNET_INTERFACE_COUNTER_IP4:
997           counter_name = "ip4";
998           break;
999         case VNET_INTERFACE_COUNTER_IP6:
1000           counter_name = "ip6";
1001           break;
1002         case VNET_INTERFACE_COUNTER_RX_NO_BUF:
1003           counter_name = "rx-no-buff";
1004           break;
1005         case VNET_INTERFACE_COUNTER_RX_MISS:
1006           , counter_name = "rx-miss";
1007           break;
1008         case VNET_INTERFACE_COUNTER_RX_ERROR:
1009           , counter_name = "rx-error (fifo-full)";
1010           break;
1011         case VNET_INTERFACE_COUNTER_TX_ERROR:
1012           , counter_name = "tx-error (fifo-full)";
1013           break;
1014         default:
1015           counter_name = "bogus";
1016           break;
1017         }
1018       for (i = 0; i < count; i++)
1019         {
1020           v = clib_mem_unaligned (vp, u64);
1021           v = clib_net_to_host_u64 (v);
1022           vp++;
1023           fformat (stdout, "%U.%s %lld\n", format_vnet_sw_if_index_name,
1024                    sm->vnet_main, sw_if_index, counter_name, v);
1025           sw_if_index++;
1026         }
1027     }
1028   else
1029     {
1030       vlib_counter_t *vp;
1031       u64 packets, bytes;
1032       vp = (vlib_counter_t *) mp->data;
1033
1034       switch (mp->vnet_counter_type)
1035         {
1036         case VNET_INTERFACE_COUNTER_RX:
1037           counter_name = "rx";
1038           break;
1039         case VNET_INTERFACE_COUNTER_TX:
1040           counter_name = "tx";
1041           break;
1042         default:
1043           counter_name = "bogus";
1044           break;
1045         }
1046       for (i = 0; i < count; i++)
1047         {
1048           packets = clib_mem_unaligned (&vp->packets, u64);
1049           packets = clib_net_to_host_u64 (packets);
1050           bytes = clib_mem_unaligned (&vp->bytes, u64);
1051           bytes = clib_net_to_host_u64 (bytes);
1052           vp++;
1053           fformat (stdout, "%U.%s.packets %lld\n",
1054                    format_vnet_sw_if_index_name,
1055                    sm->vnet_main, sw_if_index, counter_name, packets);
1056           fformat (stdout, "%U.%s.bytes %lld\n",
1057                    format_vnet_sw_if_index_name,
1058                    sm->vnet_main, sw_if_index, counter_name, bytes);
1059           sw_if_index++;
1060         }
1061     }
1062 #endif
1063   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1064     {
1065       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1066     }
1067   else
1068     {
1069       vl_msg_api_free (mp);
1070     }
1071 }
1072
1073 static void
1074 vl_api_vnet_ip4_fib_counters_t_handler (vl_api_vnet_ip4_fib_counters_t * mp)
1075 {
1076   vpe_client_registration_t *reg;
1077   stats_main_t *sm = &stats_main;
1078   unix_shared_memory_queue_t *q, *q_prev = NULL;
1079   vl_api_vnet_ip4_fib_counters_t *mp_copy = NULL;
1080   u32 mp_size;
1081
1082   mp_size = sizeof (*mp_copy) +
1083     ntohl (mp->count) * sizeof (vl_api_ip4_fib_counter_t);
1084
1085   /* *INDENT-OFF* */
1086   pool_foreach(reg, sm->stats_registrations,
1087   ({
1088     q = vl_api_client_index_to_input_queue (reg->client_index);
1089     if (q)
1090       {
1091         if (q_prev && (q_prev->cursize < q_prev->maxsize))
1092           {
1093             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
1094             clib_memcpy(mp_copy, mp, mp_size);
1095             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
1096             mp = mp_copy;
1097           }
1098         q_prev = q;
1099       }
1100   }));
1101   /* *INDENT-ON* */
1102   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1103     {
1104       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1105     }
1106   else
1107     {
1108       vl_msg_api_free (mp);
1109     }
1110 }
1111
1112 static void
1113 vl_api_vnet_ip4_nbr_counters_t_handler (vl_api_vnet_ip4_nbr_counters_t * mp)
1114 {
1115   vpe_client_registration_t *reg;
1116   stats_main_t *sm = &stats_main;
1117   unix_shared_memory_queue_t *q, *q_prev = NULL;
1118   vl_api_vnet_ip4_nbr_counters_t *mp_copy = NULL;
1119   u32 mp_size;
1120
1121   mp_size = sizeof (*mp_copy) +
1122     ntohl (mp->count) * sizeof (vl_api_ip4_nbr_counter_t);
1123
1124   /* *INDENT-OFF* */
1125   pool_foreach(reg, sm->stats_registrations,
1126   ({
1127     q = vl_api_client_index_to_input_queue (reg->client_index);
1128     if (q)
1129       {
1130         if (q_prev && (q_prev->cursize < q_prev->maxsize))
1131           {
1132             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
1133             clib_memcpy(mp_copy, mp, mp_size);
1134             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
1135             mp = mp_copy;
1136           }
1137         q_prev = q;
1138       }
1139   }));
1140   /* *INDENT-ON* */
1141   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1142     {
1143       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1144     }
1145   else
1146     {
1147       vl_msg_api_free (mp);
1148     }
1149 }
1150
1151 static void
1152 vl_api_vnet_ip6_fib_counters_t_handler (vl_api_vnet_ip6_fib_counters_t * mp)
1153 {
1154   vpe_client_registration_t *reg;
1155   stats_main_t *sm = &stats_main;
1156   unix_shared_memory_queue_t *q, *q_prev = NULL;
1157   vl_api_vnet_ip6_fib_counters_t *mp_copy = NULL;
1158   u32 mp_size;
1159
1160   mp_size = sizeof (*mp_copy) +
1161     ntohl (mp->count) * sizeof (vl_api_ip6_fib_counter_t);
1162
1163   /* *INDENT-OFF* */
1164   pool_foreach(reg, sm->stats_registrations,
1165   ({
1166     q = vl_api_client_index_to_input_queue (reg->client_index);
1167     if (q)
1168       {
1169         if (q_prev && (q_prev->cursize < q_prev->maxsize))
1170           {
1171             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
1172             clib_memcpy(mp_copy, mp, mp_size);
1173             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
1174             mp = mp_copy;
1175           }
1176         q_prev = q;
1177       }
1178   }));
1179   /* *INDENT-ON* */
1180   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1181     {
1182       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1183     }
1184   else
1185     {
1186       vl_msg_api_free (mp);
1187     }
1188 }
1189
1190 static void
1191 vl_api_vnet_ip6_nbr_counters_t_handler (vl_api_vnet_ip6_nbr_counters_t * mp)
1192 {
1193   vpe_client_registration_t *reg;
1194   stats_main_t *sm = &stats_main;
1195   unix_shared_memory_queue_t *q, *q_prev = NULL;
1196   vl_api_vnet_ip6_nbr_counters_t *mp_copy = NULL;
1197   u32 mp_size;
1198
1199   mp_size = sizeof (*mp_copy) +
1200     ntohl (mp->count) * sizeof (vl_api_ip6_nbr_counter_t);
1201
1202   /* *INDENT-OFF* */
1203   pool_foreach(reg, sm->stats_registrations,
1204   ({
1205     q = vl_api_client_index_to_input_queue (reg->client_index);
1206     if (q)
1207       {
1208         if (q_prev && (q_prev->cursize < q_prev->maxsize))
1209           {
1210             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
1211             clib_memcpy(mp_copy, mp, mp_size);
1212             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
1213             mp = mp_copy;
1214           }
1215         q_prev = q;
1216       }
1217   }));
1218   /* *INDENT-ON* */
1219   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1220     {
1221       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1222     }
1223   else
1224     {
1225       vl_msg_api_free (mp);
1226     }
1227 }
1228
1229 static void
1230 vl_api_want_stats_reply_t_handler (vl_api_want_stats_reply_t * mp)
1231 {
1232   clib_warning ("BUG");
1233 }
1234
1235 static void
1236 vl_api_want_stats_t_handler (vl_api_want_stats_t * mp)
1237 {
1238   stats_main_t *sm = &stats_main;
1239   vpe_client_registration_t *rp;
1240   vl_api_want_stats_reply_t *rmp;
1241   uword *p;
1242   i32 retval = 0;
1243   unix_shared_memory_queue_t *q;
1244
1245   p = hash_get (sm->stats_registration_hash, mp->client_index);
1246   if (p)
1247     {
1248       if (mp->enable_disable)
1249         {
1250           clib_warning ("pid %d: already enabled...", mp->pid);
1251           retval = -2;
1252           goto reply;
1253         }
1254       else
1255         {
1256           rp = pool_elt_at_index (sm->stats_registrations, p[0]);
1257           pool_put (sm->stats_registrations, rp);
1258           hash_unset (sm->stats_registration_hash, mp->client_index);
1259           goto reply;
1260         }
1261     }
1262   if (mp->enable_disable == 0)
1263     {
1264       clib_warning ("pid %d: already disabled...", mp->pid);
1265       retval = -3;
1266       goto reply;
1267     }
1268   pool_get (sm->stats_registrations, rp);
1269   rp->client_index = mp->client_index;
1270   rp->client_pid = mp->pid;
1271   hash_set (sm->stats_registration_hash, rp->client_index,
1272             rp - sm->stats_registrations);
1273
1274 reply:
1275   if (pool_elts (sm->stats_registrations))
1276     sm->enable_poller = 1;
1277   else
1278     sm->enable_poller = 0;
1279
1280   q = vl_api_client_index_to_input_queue (mp->client_index);
1281
1282   if (!q)
1283     return;
1284
1285   rmp = vl_msg_api_alloc (sizeof (*rmp));
1286   rmp->_vl_msg_id = ntohs (VL_API_WANT_STATS_REPLY);
1287   rmp->context = mp->context;
1288   rmp->retval = retval;
1289
1290   vl_msg_api_send_shmem (q, (u8 *) & rmp);
1291 }
1292
1293 int
1294 stats_memclnt_delete_callback (u32 client_index)
1295 {
1296   vpe_client_registration_t *rp;
1297   stats_main_t *sm = &stats_main;
1298   uword *p;
1299
1300   p = hash_get (sm->stats_registration_hash, client_index);
1301   if (p)
1302     {
1303       rp = pool_elt_at_index (sm->stats_registrations, p[0]);
1304       pool_put (sm->stats_registrations, rp);
1305       hash_unset (sm->stats_registration_hash, client_index);
1306     }
1307
1308   return 0;
1309 }
1310
1311 #define vl_api_vnet_ip4_fib_counters_t_endian vl_noop_handler
1312 #define vl_api_vnet_ip4_fib_counters_t_print vl_noop_handler
1313 #define vl_api_vnet_ip6_fib_counters_t_endian vl_noop_handler
1314 #define vl_api_vnet_ip6_fib_counters_t_print vl_noop_handler
1315 #define vl_api_vnet_ip4_nbr_counters_t_endian vl_noop_handler
1316 #define vl_api_vnet_ip4_nbr_counters_t_print vl_noop_handler
1317 #define vl_api_vnet_ip6_nbr_counters_t_endian vl_noop_handler
1318 #define vl_api_vnet_ip6_nbr_counters_t_print vl_noop_handler
1319
1320 static clib_error_t *
1321 stats_init (vlib_main_t * vm)
1322 {
1323   stats_main_t *sm = &stats_main;
1324   api_main_t *am = &api_main;
1325   void *vlib_worker_thread_bootstrap_fn (void *arg);
1326
1327   sm->vlib_main = vm;
1328   sm->vnet_main = vnet_get_main ();
1329   sm->interface_main = &vnet_get_main ()->interface_main;
1330   sm->api_main = am;
1331   sm->stats_poll_interval_in_seconds = 10;
1332   sm->data_structure_lock =
1333     clib_mem_alloc_aligned (sizeof (data_structure_lock_t),
1334                             CLIB_CACHE_LINE_BYTES);
1335   memset (sm->data_structure_lock, 0, sizeof (*sm->data_structure_lock));
1336
1337 #define _(N,n)                                                  \
1338     vl_msg_api_set_handlers(VL_API_##N, #n,                     \
1339                            vl_api_##n##_t_handler,              \
1340                            vl_noop_handler,                     \
1341                            vl_api_##n##_t_endian,               \
1342                            vl_api_##n##_t_print,                \
1343                            sizeof(vl_api_##n##_t), 0 /* do NOT trace! */);
1344   foreach_stats_msg;
1345 #undef _
1346
1347   /* tell the msg infra not to free these messages... */
1348   am->message_bounce[VL_API_VNET_INTERFACE_COUNTERS] = 1;
1349   am->message_bounce[VL_API_VNET_IP4_FIB_COUNTERS] = 1;
1350   am->message_bounce[VL_API_VNET_IP6_FIB_COUNTERS] = 1;
1351   am->message_bounce[VL_API_VNET_IP4_NBR_COUNTERS] = 1;
1352   am->message_bounce[VL_API_VNET_IP6_NBR_COUNTERS] = 1;
1353
1354   return 0;
1355 }
1356
1357 VLIB_INIT_FUNCTION (stats_init);
1358
1359 /* *INDENT-OFF* */
1360 VLIB_REGISTER_THREAD (stats_thread_reg, static) = {
1361   .name = "stats",
1362   .function = stats_thread_fn,
1363   .fixed_count = 1,
1364   .count = 1,
1365   .no_data_structure_clone = 1,
1366   .use_pthreads = 1,
1367 };
1368 /* *INDENT-ON* */
1369
1370 /*
1371  * fd.io coding-style-patch-verification: ON
1372  *
1373  * Local Variables:
1374  * eval: (c-set-style "gnu")
1375  * End:
1376  */