Use thread local storage for thread index
[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/fib/ip4_fib.h>
21 #include <vnet/dpo/load_balance.h>
22
23 #define STATS_DEBUG 0
24
25 stats_main_t stats_main;
26
27 #include <vnet/ip/ip.h>
28
29 #include <vpp/api/vpe_msg_enum.h>
30
31 #define f64_endian(a)
32 #define f64_print(a,b)
33
34 #define vl_typedefs             /* define message structures */
35 #include <vpp/api/vpe_all_api_h.h>
36 #undef vl_typedefs
37
38 #define vl_endianfun            /* define message structures */
39 #include <vpp/api/vpe_all_api_h.h>
40 #undef vl_endianfun
41
42 /* instantiate all the print functions we know about */
43 #define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
44 #define vl_printfun
45 #include <vpp/api/vpe_all_api_h.h>
46 #undef vl_printfun
47
48 #define foreach_stats_msg                               \
49 _(WANT_STATS, want_stats)                               \
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_index;
70   data_structure_lock_t *l = sm->data_structure_lock;
71
72   if (PREDICT_FALSE (l == 0))
73     return;
74
75   thread_index = vlib_get_thread_index ();
76   if (l->lock && l->thread_index == thread_index)
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_index = thread_index;
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_index;
103   data_structure_lock_t *l = sm->data_structure_lock;
104
105   if (PREDICT_FALSE (l == 0))
106     return;
107
108   thread_index = vlib_get_thread_index ();
109   ASSERT (l->lock && l->thread_index == thread_index);
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, n_counts;
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     n_counts = vlib_simple_counter_n_counters (cm);
149     for (i = 0; i < n_counts; i++)
150       {
151         if (mp == 0)
152           {
153             items_this_message = clib_min (SIMPLE_COUNTER_BATCH_SIZE,
154                                            n_counts - 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, n_counts;
194
195   vnet_interface_counter_lock (im);
196
197   vec_foreach (cm, im->combined_sw_if_counters)
198   {
199     n_counts = vlib_combined_counter_n_counters (cm);
200     for (i = 0; i < n_counts; i++)
201       {
202         if (mp == 0)
203           {
204             items_this_message = clib_min (COMBINED_COUNTER_BATCH_SIZE,
205                                            n_counts - 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   ip4_fib_t *v4_fib;
581   ip_lookup_main_t *lm = &im4->lookup_main;
582   static uword *results;
583   vl_api_vnet_ip4_fib_counters_t *mp = 0;
584   u32 items_this_message;
585   vl_api_ip4_fib_counter_t *ctrp = 0;
586   u32 start_at_fib_index = 0;
587   int i;
588
589 again:
590   /* *INDENT-OFF* */
591   pool_foreach (fib, im4->fibs,
592   ({
593     /* We may have bailed out due to control-plane activity */
594     while ((fib - im4->fibs) < start_at_fib_index)
595       continue;
596
597     v4_fib = pool_elt_at_index (im4->v4_fibs, fib->ft_index);
598
599     if (mp == 0)
600       {
601         items_this_message = IP4_FIB_COUNTER_BATCH_SIZE;
602         mp = vl_msg_api_alloc_as_if_client
603           (sizeof (*mp) +
604            items_this_message * sizeof (vl_api_ip4_fib_counter_t));
605         mp->_vl_msg_id = ntohs (VL_API_VNET_IP4_FIB_COUNTERS);
606         mp->count = 0;
607         mp->vrf_id = ntohl (fib->ft_table_id);
608         ctrp = (vl_api_ip4_fib_counter_t *) mp->c;
609       }
610     else
611       {
612         /* happens if the last FIB was empty... */
613         ASSERT (mp->count == 0);
614         mp->vrf_id = ntohl (fib->ft_table_id);
615       }
616
617     dslock (sm, 0 /* release hint */ , 1 /* tag */ );
618
619     vec_reset_length (routes);
620     vec_reset_length (results);
621
622     for (i = 0; i < ARRAY_LEN (v4_fib->fib_entry_by_dst_address); i++)
623       {
624         uword *hash = v4_fib->fib_entry_by_dst_address[i];
625         hash_pair_t *p;
626         ip4_route_t x;
627
628         x.address_length = i;
629
630         hash_foreach_pair (p, hash,
631         ({
632           x.address.data_u32 = p->key;
633           x.index = p->value[0];
634
635           vec_add1 (routes, x);
636           if (sm->data_structure_lock->release_hint)
637             {
638               start_at_fib_index = fib - im4->fibs;
639               dsunlock (sm);
640               ip46_fib_stats_delay (sm, 0 /* sec */,
641                                     STATS_RELEASE_DELAY_NS);
642               mp->count = 0;
643               ctrp = (vl_api_ip4_fib_counter_t *)mp->c;
644               goto again;
645             }
646         }));
647       }
648
649     vec_foreach (r, routes)
650       {
651         vlib_counter_t c;
652
653         vlib_get_combined_counter (&load_balance_main.lbm_to_counters,
654                                    r->index, &c);
655         /*
656          * If it has actually
657          * seen at least one packet, send it.
658          */
659         if (c.packets > 0)
660           {
661
662             /* already in net byte order */
663             ctrp->address = r->address.as_u32;
664             ctrp->address_length = r->address_length;
665             ctrp->packets = clib_host_to_net_u64 (c.packets);
666             ctrp->bytes = clib_host_to_net_u64 (c.bytes);
667             mp->count++;
668             ctrp++;
669
670             if (mp->count == items_this_message)
671               {
672                 mp->count = htonl (items_this_message);
673                 /*
674                  * If the main thread's input queue is stuffed,
675                  * drop the data structure lock (which the main thread
676                  * may want), and take a pause.
677                  */
678                 unix_shared_memory_queue_lock (q);
679                 if (unix_shared_memory_queue_is_full (q))
680                   {
681                     dsunlock (sm);
682                     vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
683                     unix_shared_memory_queue_unlock (q);
684                     mp = 0;
685                     ip46_fib_stats_delay (sm, 0 /* sec */ ,
686                                           STATS_RELEASE_DELAY_NS);
687                     goto again;
688                   }
689                 vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
690                 unix_shared_memory_queue_unlock (q);
691
692                 items_this_message = IP4_FIB_COUNTER_BATCH_SIZE;
693                 mp = vl_msg_api_alloc_as_if_client
694                   (sizeof (*mp) +
695                    items_this_message * sizeof (vl_api_ip4_fib_counter_t));
696                 mp->_vl_msg_id = ntohs (VL_API_VNET_IP4_FIB_COUNTERS);
697                 mp->count = 0;
698                 mp->vrf_id = ntohl (fib->ft_table_id);
699                 ctrp = (vl_api_ip4_fib_counter_t *) mp->c;
700               }
701           }                     /* for each (mp or single) adj */
702         if (sm->data_structure_lock->release_hint)
703           {
704             start_at_fib_index = fib - im4->fibs;
705             dsunlock (sm);
706             ip46_fib_stats_delay (sm, 0 /* sec */ , STATS_RELEASE_DELAY_NS);
707             mp->count = 0;
708             ctrp = (vl_api_ip4_fib_counter_t *) mp->c;
709             goto again;
710           }
711       }                         /* vec_foreach (routes) */
712
713     dsunlock (sm);
714
715     /* Flush any data from this fib */
716     if (mp->count)
717       {
718         mp->count = htonl (mp->count);
719         vl_msg_api_send_shmem (q, (u8 *) & mp);
720         mp = 0;
721       }
722   }));
723   /* *INDENT-ON* */
724
725   /* If e.g. the last FIB had no reportable routes, free the buffer */
726   if (mp)
727     vl_msg_api_free (mp);
728 }
729
730 typedef struct
731 {
732   ip6_address_t address;
733   u32 address_length;
734   u32 index;
735 } ip6_route_t;
736
737 typedef struct
738 {
739   u32 fib_index;
740   ip6_route_t **routep;
741   stats_main_t *sm;
742 } add_routes_in_fib_arg_t;
743
744 static void
745 add_routes_in_fib (BVT (clib_bihash_kv) * kvp, void *arg)
746 {
747   add_routes_in_fib_arg_t *ap = arg;
748   stats_main_t *sm = ap->sm;
749
750   if (sm->data_structure_lock->release_hint)
751     clib_longjmp (&sm->jmp_buf, 1);
752
753   if (kvp->key[2] >> 32 == ap->fib_index)
754     {
755       ip6_address_t *addr;
756       ip6_route_t *r;
757       addr = (ip6_address_t *) kvp;
758       vec_add2 (*ap->routep, r, 1);
759       r->address = addr[0];
760       r->address_length = kvp->key[2] & 0xFF;
761       r->index = kvp->value;
762     }
763 }
764
765 static void
766 do_ip6_fibs (stats_main_t * sm)
767 {
768   ip6_main_t *im6 = &ip6_main;
769   api_main_t *am = sm->api_main;
770   vl_shmem_hdr_t *shmem_hdr = am->shmem_hdr;
771   unix_shared_memory_queue_t *q = shmem_hdr->vl_input_queue;
772   static ip6_route_t *routes;
773   ip6_route_t *r;
774   fib_table_t *fib;
775   static uword *results;
776   vl_api_vnet_ip6_fib_counters_t *mp = 0;
777   u32 items_this_message;
778   vl_api_ip6_fib_counter_t *ctrp = 0;
779   u32 start_at_fib_index = 0;
780   BVT (clib_bihash) * h = &im6->ip6_table[IP6_FIB_TABLE_FWDING].ip6_hash;
781   add_routes_in_fib_arg_t _a, *a = &_a;
782
783 again:
784   /* *INDENT-OFF* */
785   pool_foreach (fib, im6->fibs,
786   ({
787     /* We may have bailed out due to control-plane activity */
788     while ((fib - im6->fibs) < start_at_fib_index)
789       continue;
790
791     if (mp == 0)
792       {
793         items_this_message = IP6_FIB_COUNTER_BATCH_SIZE;
794         mp = vl_msg_api_alloc_as_if_client
795           (sizeof (*mp) +
796            items_this_message * sizeof (vl_api_ip6_fib_counter_t));
797         mp->_vl_msg_id = ntohs (VL_API_VNET_IP6_FIB_COUNTERS);
798         mp->count = 0;
799         mp->vrf_id = ntohl (fib->ft_table_id);
800         ctrp = (vl_api_ip6_fib_counter_t *) mp->c;
801       }
802
803     dslock (sm, 0 /* release hint */ , 1 /* tag */ );
804
805     vec_reset_length (routes);
806     vec_reset_length (results);
807
808     a->fib_index = fib - im6->fibs;
809     a->routep = &routes;
810     a->sm = sm;
811
812     if (clib_setjmp (&sm->jmp_buf, 0) == 0)
813       {
814         start_at_fib_index = fib - im6->fibs;
815         BV (clib_bihash_foreach_key_value_pair) (h, add_routes_in_fib, a);
816       }
817     else
818       {
819         dsunlock (sm);
820         ip46_fib_stats_delay (sm, 0 /* sec */ ,
821                               STATS_RELEASE_DELAY_NS);
822         mp->count = 0;
823         ctrp = (vl_api_ip6_fib_counter_t *) mp->c;
824         goto again;
825       }
826
827     vec_foreach (r, routes)
828     {
829         vlib_counter_t c;
830
831         vlib_get_combined_counter (&load_balance_main.lbm_to_counters,
832                                    r->index, &c);
833         /*
834          * If it has actually
835          * seen at least one packet, send it.
836          */
837         if (c.packets > 0)
838           {
839             /* already in net byte order */
840             ctrp->address[0] = r->address.as_u64[0];
841             ctrp->address[1] = r->address.as_u64[1];
842             ctrp->address_length = (u8) r->address_length;
843             ctrp->packets = clib_host_to_net_u64 (c.packets);
844             ctrp->bytes = clib_host_to_net_u64 (c.bytes);
845             mp->count++;
846             ctrp++;
847
848             if (mp->count == items_this_message)
849               {
850                 mp->count = htonl (items_this_message);
851                 /*
852                  * If the main thread's input queue is stuffed,
853                  * drop the data structure lock (which the main thread
854                  * may want), and take a pause.
855                  */
856                 unix_shared_memory_queue_lock (q);
857                 if (unix_shared_memory_queue_is_full (q))
858                   {
859                     dsunlock (sm);
860                     vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
861                     unix_shared_memory_queue_unlock (q);
862                     mp = 0;
863                     ip46_fib_stats_delay (sm, 0 /* sec */ ,
864                                           STATS_RELEASE_DELAY_NS);
865                     goto again;
866                   }
867                 vl_msg_api_send_shmem_nolock (q, (u8 *) & mp);
868                 unix_shared_memory_queue_unlock (q);
869
870                 items_this_message = IP6_FIB_COUNTER_BATCH_SIZE;
871                 mp = vl_msg_api_alloc_as_if_client
872                   (sizeof (*mp) +
873                    items_this_message * sizeof (vl_api_ip6_fib_counter_t));
874                 mp->_vl_msg_id = ntohs (VL_API_VNET_IP6_FIB_COUNTERS);
875                 mp->count = 0;
876                 mp->vrf_id = ntohl (fib->ft_table_id);
877                 ctrp = (vl_api_ip6_fib_counter_t *) mp->c;
878               }
879           }
880
881         if (sm->data_structure_lock->release_hint)
882           {
883             start_at_fib_index = fib - im6->fibs;
884             dsunlock (sm);
885             ip46_fib_stats_delay (sm, 0 /* sec */ , STATS_RELEASE_DELAY_NS);
886             mp->count = 0;
887             ctrp = (vl_api_ip6_fib_counter_t *) mp->c;
888             goto again;
889           }
890     }                           /* vec_foreach (routes) */
891
892     dsunlock (sm);
893
894     /* Flush any data from this fib */
895     if (mp->count)
896       {
897         mp->count = htonl (mp->count);
898         vl_msg_api_send_shmem (q, (u8 *) & mp);
899         mp = 0;
900       }
901   }));
902   /* *INDENT-ON* */
903
904   /* If e.g. the last FIB had no reportable routes, free the buffer */
905   if (mp)
906     vl_msg_api_free (mp);
907 }
908
909 static void
910 stats_thread_fn (void *arg)
911 {
912   stats_main_t *sm = &stats_main;
913   vlib_worker_thread_t *w = (vlib_worker_thread_t *) arg;
914   vlib_thread_main_t *tm = vlib_get_thread_main ();
915
916   /* stats thread wants no signals. */
917   {
918     sigset_t s;
919     sigfillset (&s);
920     pthread_sigmask (SIG_SETMASK, &s, 0);
921   }
922
923   if (vec_len (tm->thread_prefix))
924     vlib_set_thread_name ((char *)
925                           format (0, "%v_stats%c", tm->thread_prefix, '\0'));
926
927   clib_mem_set_heap (w->thread_mheap);
928
929   while (1)
930     {
931       /* 10 second poll interval */
932       ip46_fib_stats_delay (sm, 10 /* secs */ , 0 /* nsec */ );
933
934       if (!(sm->enable_poller))
935         continue;
936       do_simple_interface_counters (sm);
937       do_combined_interface_counters (sm);
938       do_ip4_fibs (sm);
939       do_ip6_fibs (sm);
940       do_ip4_nbrs (sm);
941       do_ip6_nbrs (sm);
942     }
943 }
944
945 static void
946 vl_api_vnet_interface_counters_t_handler (vl_api_vnet_interface_counters_t *
947                                           mp)
948 {
949   vpe_client_registration_t *reg;
950   stats_main_t *sm = &stats_main;
951   unix_shared_memory_queue_t *q, *q_prev = NULL;
952   vl_api_vnet_interface_counters_t *mp_copy = NULL;
953   u32 mp_size;
954
955 #if STATS_DEBUG > 0
956   char *counter_name;
957   u32 count, sw_if_index;
958   int i;
959 #endif
960
961   mp_size = sizeof (*mp) + (ntohl (mp->count) *
962                             (mp->is_combined ? sizeof (vlib_counter_t) :
963                              sizeof (u64)));
964
965   /* *INDENT-OFF* */
966   pool_foreach(reg, sm->stats_registrations,
967   ({
968     q = vl_api_client_index_to_input_queue (reg->client_index);
969     if (q)
970       {
971         if (q_prev && (q_prev->cursize < q_prev->maxsize))
972           {
973             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
974             clib_memcpy(mp_copy, mp, mp_size);
975             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
976             mp = mp_copy;
977           }
978         q_prev = q;
979       }
980   }));
981   /* *INDENT-ON* */
982
983 #if STATS_DEBUG > 0
984   count = ntohl (mp->count);
985   sw_if_index = ntohl (mp->first_sw_if_index);
986   if (mp->is_combined == 0)
987     {
988       u64 *vp, v;
989       vp = (u64 *) mp->data;
990
991       switch (mp->vnet_counter_type)
992         {
993         case VNET_INTERFACE_COUNTER_DROP:
994           counter_name = "drop";
995           break;
996         case VNET_INTERFACE_COUNTER_PUNT:
997           counter_name = "punt";
998           break;
999         case VNET_INTERFACE_COUNTER_IP4:
1000           counter_name = "ip4";
1001           break;
1002         case VNET_INTERFACE_COUNTER_IP6:
1003           counter_name = "ip6";
1004           break;
1005         case VNET_INTERFACE_COUNTER_RX_NO_BUF:
1006           counter_name = "rx-no-buff";
1007           break;
1008         case VNET_INTERFACE_COUNTER_RX_MISS:
1009           , counter_name = "rx-miss";
1010           break;
1011         case VNET_INTERFACE_COUNTER_RX_ERROR:
1012           , counter_name = "rx-error (fifo-full)";
1013           break;
1014         case VNET_INTERFACE_COUNTER_TX_ERROR:
1015           , counter_name = "tx-error (fifo-full)";
1016           break;
1017         default:
1018           counter_name = "bogus";
1019           break;
1020         }
1021       for (i = 0; i < count; i++)
1022         {
1023           v = clib_mem_unaligned (vp, u64);
1024           v = clib_net_to_host_u64 (v);
1025           vp++;
1026           fformat (stdout, "%U.%s %lld\n", format_vnet_sw_if_index_name,
1027                    sm->vnet_main, sw_if_index, counter_name, v);
1028           sw_if_index++;
1029         }
1030     }
1031   else
1032     {
1033       vlib_counter_t *vp;
1034       u64 packets, bytes;
1035       vp = (vlib_counter_t *) mp->data;
1036
1037       switch (mp->vnet_counter_type)
1038         {
1039         case VNET_INTERFACE_COUNTER_RX:
1040           counter_name = "rx";
1041           break;
1042         case VNET_INTERFACE_COUNTER_TX:
1043           counter_name = "tx";
1044           break;
1045         default:
1046           counter_name = "bogus";
1047           break;
1048         }
1049       for (i = 0; i < count; i++)
1050         {
1051           packets = clib_mem_unaligned (&vp->packets, u64);
1052           packets = clib_net_to_host_u64 (packets);
1053           bytes = clib_mem_unaligned (&vp->bytes, u64);
1054           bytes = clib_net_to_host_u64 (bytes);
1055           vp++;
1056           fformat (stdout, "%U.%s.packets %lld\n",
1057                    format_vnet_sw_if_index_name,
1058                    sm->vnet_main, sw_if_index, counter_name, packets);
1059           fformat (stdout, "%U.%s.bytes %lld\n",
1060                    format_vnet_sw_if_index_name,
1061                    sm->vnet_main, sw_if_index, counter_name, bytes);
1062           sw_if_index++;
1063         }
1064     }
1065 #endif
1066   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1067     {
1068       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1069     }
1070   else
1071     {
1072       vl_msg_api_free (mp);
1073     }
1074 }
1075
1076 static void
1077 vl_api_vnet_ip4_fib_counters_t_handler (vl_api_vnet_ip4_fib_counters_t * mp)
1078 {
1079   vpe_client_registration_t *reg;
1080   stats_main_t *sm = &stats_main;
1081   unix_shared_memory_queue_t *q, *q_prev = NULL;
1082   vl_api_vnet_ip4_fib_counters_t *mp_copy = NULL;
1083   u32 mp_size;
1084
1085   mp_size = sizeof (*mp_copy) +
1086     ntohl (mp->count) * sizeof (vl_api_ip4_fib_counter_t);
1087
1088   /* *INDENT-OFF* */
1089   pool_foreach(reg, sm->stats_registrations,
1090   ({
1091     q = vl_api_client_index_to_input_queue (reg->client_index);
1092     if (q)
1093       {
1094         if (q_prev && (q_prev->cursize < q_prev->maxsize))
1095           {
1096             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
1097             clib_memcpy(mp_copy, mp, mp_size);
1098             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
1099             mp = mp_copy;
1100           }
1101         q_prev = q;
1102       }
1103   }));
1104   /* *INDENT-ON* */
1105   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1106     {
1107       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1108     }
1109   else
1110     {
1111       vl_msg_api_free (mp);
1112     }
1113 }
1114
1115 static void
1116 vl_api_vnet_ip4_nbr_counters_t_handler (vl_api_vnet_ip4_nbr_counters_t * mp)
1117 {
1118   vpe_client_registration_t *reg;
1119   stats_main_t *sm = &stats_main;
1120   unix_shared_memory_queue_t *q, *q_prev = NULL;
1121   vl_api_vnet_ip4_nbr_counters_t *mp_copy = NULL;
1122   u32 mp_size;
1123
1124   mp_size = sizeof (*mp_copy) +
1125     ntohl (mp->count) * sizeof (vl_api_ip4_nbr_counter_t);
1126
1127   /* *INDENT-OFF* */
1128   pool_foreach(reg, sm->stats_registrations,
1129   ({
1130     q = vl_api_client_index_to_input_queue (reg->client_index);
1131     if (q)
1132       {
1133         if (q_prev && (q_prev->cursize < q_prev->maxsize))
1134           {
1135             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
1136             clib_memcpy(mp_copy, mp, mp_size);
1137             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
1138             mp = mp_copy;
1139           }
1140         q_prev = q;
1141       }
1142   }));
1143   /* *INDENT-ON* */
1144   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1145     {
1146       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1147     }
1148   else
1149     {
1150       vl_msg_api_free (mp);
1151     }
1152 }
1153
1154 static void
1155 vl_api_vnet_ip6_fib_counters_t_handler (vl_api_vnet_ip6_fib_counters_t * mp)
1156 {
1157   vpe_client_registration_t *reg;
1158   stats_main_t *sm = &stats_main;
1159   unix_shared_memory_queue_t *q, *q_prev = NULL;
1160   vl_api_vnet_ip6_fib_counters_t *mp_copy = NULL;
1161   u32 mp_size;
1162
1163   mp_size = sizeof (*mp_copy) +
1164     ntohl (mp->count) * sizeof (vl_api_ip6_fib_counter_t);
1165
1166   /* *INDENT-OFF* */
1167   pool_foreach(reg, sm->stats_registrations,
1168   ({
1169     q = vl_api_client_index_to_input_queue (reg->client_index);
1170     if (q)
1171       {
1172         if (q_prev && (q_prev->cursize < q_prev->maxsize))
1173           {
1174             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
1175             clib_memcpy(mp_copy, mp, mp_size);
1176             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
1177             mp = mp_copy;
1178           }
1179         q_prev = q;
1180       }
1181   }));
1182   /* *INDENT-ON* */
1183   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1184     {
1185       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1186     }
1187   else
1188     {
1189       vl_msg_api_free (mp);
1190     }
1191 }
1192
1193 static void
1194 vl_api_vnet_ip6_nbr_counters_t_handler (vl_api_vnet_ip6_nbr_counters_t * mp)
1195 {
1196   vpe_client_registration_t *reg;
1197   stats_main_t *sm = &stats_main;
1198   unix_shared_memory_queue_t *q, *q_prev = NULL;
1199   vl_api_vnet_ip6_nbr_counters_t *mp_copy = NULL;
1200   u32 mp_size;
1201
1202   mp_size = sizeof (*mp_copy) +
1203     ntohl (mp->count) * sizeof (vl_api_ip6_nbr_counter_t);
1204
1205   /* *INDENT-OFF* */
1206   pool_foreach(reg, sm->stats_registrations,
1207   ({
1208     q = vl_api_client_index_to_input_queue (reg->client_index);
1209     if (q)
1210       {
1211         if (q_prev && (q_prev->cursize < q_prev->maxsize))
1212           {
1213             mp_copy = vl_msg_api_alloc_as_if_client(mp_size);
1214             clib_memcpy(mp_copy, mp, mp_size);
1215             vl_msg_api_send_shmem (q_prev, (u8 *)&mp);
1216             mp = mp_copy;
1217           }
1218         q_prev = q;
1219       }
1220   }));
1221   /* *INDENT-ON* */
1222   if (q_prev && (q_prev->cursize < q_prev->maxsize))
1223     {
1224       vl_msg_api_send_shmem (q_prev, (u8 *) & mp);
1225     }
1226   else
1227     {
1228       vl_msg_api_free (mp);
1229     }
1230 }
1231
1232 static void
1233 vl_api_want_stats_t_handler (vl_api_want_stats_t * mp)
1234 {
1235   stats_main_t *sm = &stats_main;
1236   vpe_client_registration_t *rp;
1237   vl_api_want_stats_reply_t *rmp;
1238   uword *p;
1239   i32 retval = 0;
1240   unix_shared_memory_queue_t *q;
1241
1242   p = hash_get (sm->stats_registration_hash, mp->client_index);
1243   if (p)
1244     {
1245       if (mp->enable_disable)
1246         {
1247           clib_warning ("pid %d: already enabled...", mp->pid);
1248           retval = -2;
1249           goto reply;
1250         }
1251       else
1252         {
1253           rp = pool_elt_at_index (sm->stats_registrations, p[0]);
1254           pool_put (sm->stats_registrations, rp);
1255           hash_unset (sm->stats_registration_hash, mp->client_index);
1256           goto reply;
1257         }
1258     }
1259   if (mp->enable_disable == 0)
1260     {
1261       clib_warning ("pid %d: already disabled...", mp->pid);
1262       retval = -3;
1263       goto reply;
1264     }
1265   pool_get (sm->stats_registrations, rp);
1266   rp->client_index = mp->client_index;
1267   rp->client_pid = mp->pid;
1268   hash_set (sm->stats_registration_hash, rp->client_index,
1269             rp - sm->stats_registrations);
1270
1271 reply:
1272   if (pool_elts (sm->stats_registrations))
1273     sm->enable_poller = 1;
1274   else
1275     sm->enable_poller = 0;
1276
1277   q = vl_api_client_index_to_input_queue (mp->client_index);
1278
1279   if (!q)
1280     return;
1281
1282   rmp = vl_msg_api_alloc (sizeof (*rmp));
1283   rmp->_vl_msg_id = ntohs (VL_API_WANT_STATS_REPLY);
1284   rmp->context = mp->context;
1285   rmp->retval = retval;
1286
1287   vl_msg_api_send_shmem (q, (u8 *) & rmp);
1288 }
1289
1290 int
1291 stats_memclnt_delete_callback (u32 client_index)
1292 {
1293   vpe_client_registration_t *rp;
1294   stats_main_t *sm = &stats_main;
1295   uword *p;
1296
1297   p = hash_get (sm->stats_registration_hash, client_index);
1298   if (p)
1299     {
1300       rp = pool_elt_at_index (sm->stats_registrations, p[0]);
1301       pool_put (sm->stats_registrations, rp);
1302       hash_unset (sm->stats_registration_hash, client_index);
1303     }
1304
1305   return 0;
1306 }
1307
1308 #define vl_api_vnet_ip4_fib_counters_t_endian vl_noop_handler
1309 #define vl_api_vnet_ip4_fib_counters_t_print vl_noop_handler
1310 #define vl_api_vnet_ip6_fib_counters_t_endian vl_noop_handler
1311 #define vl_api_vnet_ip6_fib_counters_t_print vl_noop_handler
1312 #define vl_api_vnet_ip4_nbr_counters_t_endian vl_noop_handler
1313 #define vl_api_vnet_ip4_nbr_counters_t_print vl_noop_handler
1314 #define vl_api_vnet_ip6_nbr_counters_t_endian vl_noop_handler
1315 #define vl_api_vnet_ip6_nbr_counters_t_print vl_noop_handler
1316
1317 static clib_error_t *
1318 stats_init (vlib_main_t * vm)
1319 {
1320   stats_main_t *sm = &stats_main;
1321   api_main_t *am = &api_main;
1322   void *vlib_worker_thread_bootstrap_fn (void *arg);
1323
1324   sm->vlib_main = vm;
1325   sm->vnet_main = vnet_get_main ();
1326   sm->interface_main = &vnet_get_main ()->interface_main;
1327   sm->api_main = am;
1328   sm->stats_poll_interval_in_seconds = 10;
1329   sm->data_structure_lock =
1330     clib_mem_alloc_aligned (sizeof (data_structure_lock_t),
1331                             CLIB_CACHE_LINE_BYTES);
1332   memset (sm->data_structure_lock, 0, sizeof (*sm->data_structure_lock));
1333
1334 #define _(N,n)                                                  \
1335     vl_msg_api_set_handlers(VL_API_##N, #n,                     \
1336                            vl_api_##n##_t_handler,              \
1337                            vl_noop_handler,                     \
1338                            vl_api_##n##_t_endian,               \
1339                            vl_api_##n##_t_print,                \
1340                            sizeof(vl_api_##n##_t), 0 /* do NOT trace! */);
1341   foreach_stats_msg;
1342 #undef _
1343
1344   /* tell the msg infra not to free these messages... */
1345   am->message_bounce[VL_API_VNET_INTERFACE_COUNTERS] = 1;
1346   am->message_bounce[VL_API_VNET_IP4_FIB_COUNTERS] = 1;
1347   am->message_bounce[VL_API_VNET_IP6_FIB_COUNTERS] = 1;
1348   am->message_bounce[VL_API_VNET_IP4_NBR_COUNTERS] = 1;
1349   am->message_bounce[VL_API_VNET_IP6_NBR_COUNTERS] = 1;
1350
1351   return 0;
1352 }
1353
1354 VLIB_INIT_FUNCTION (stats_init);
1355
1356 /* *INDENT-OFF* */
1357 VLIB_REGISTER_THREAD (stats_thread_reg, static) = {
1358   .name = "stats",
1359   .function = stats_thread_fn,
1360   .fixed_count = 1,
1361   .count = 1,
1362   .no_data_structure_clone = 1,
1363   .use_pthreads = 1,
1364 };
1365 /* *INDENT-ON* */
1366
1367 /*
1368  * fd.io coding-style-patch-verification: ON
1369  *
1370  * Local Variables:
1371  * eval: (c-set-style "gnu")
1372  * End:
1373  */