Crude stat segment lock recovery
[vpp.git] / src / vpp / stats / stat_segment.c
1 /*
2  * Copyright (c) 2018 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
17 void
18 vlib_stat_segment_lock (void)
19 {
20   stats_main_t *sm = &stats_main;
21   vlib_main_t *vm = vlib_get_main ();
22   f64 deadman;
23
24   /* 3ms is WAY long enough to be reasonably sure something is wrong */
25   deadman = vlib_time_now (vm) + 3e-3;
26
27   while (__sync_lock_test_and_set (&((*sm->stat_segment_lockp)->lock), 1))
28     {
29       if (vlib_time_now (vm) >= deadman)
30         {
31           clib_warning ("BUG: stat segment lock held too long...");
32           break;
33         }
34     }
35 }
36
37 void
38 vlib_stat_segment_unlock (void)
39 {
40   stats_main_t *sm = &stats_main;
41   clib_spinlock_unlock (sm->stat_segment_lockp);
42 }
43
44 void *
45 vlib_stats_push_heap (void)
46 {
47   stats_main_t *sm = &stats_main;
48   ssvm_private_t *ssvmp = &sm->stat_segment;
49   ssvm_shared_header_t *shared_header;
50
51   ASSERT (ssvmp && ssvmp->sh);
52
53   shared_header = ssvmp->sh;
54
55   return ssvm_push_heap (shared_header);
56 }
57
58 void
59 vlib_stats_pop_heap (void *cm_arg, void *oldheap, stat_directory_type_t type)
60 {
61   vlib_simple_counter_main_t *cm = (vlib_simple_counter_main_t *) cm_arg;
62   stats_main_t *sm = &stats_main;
63   ssvm_private_t *ssvmp = &sm->stat_segment;
64   ssvm_shared_header_t *shared_header;
65   char *stat_segment_name;
66   stat_segment_directory_entry_t *ep;
67   uword *p;
68
69   ASSERT (ssvmp && ssvmp->sh);
70
71   shared_header = ssvmp->sh;
72
73   /* Not all counters have names / hash-table entries */
74   if (cm->name || cm->stat_segment_name)
75     {
76       hash_pair_t *hp;
77       u8 *name_copy;
78
79       stat_segment_name = cm->stat_segment_name ?
80         cm->stat_segment_name : cm->name;
81
82       vlib_stat_segment_lock ();
83
84       /* Update hash table. The name must be copied into the segment */
85       hp = hash_get_pair (sm->counter_vector_by_name, stat_segment_name);
86       if (hp)
87         {
88           name_copy = (u8 *) hp->key;
89           ep = (stat_segment_directory_entry_t *) (hp->value[0]);
90           hash_unset_mem (sm->counter_vector_by_name, stat_segment_name);
91           vec_free (name_copy);
92           clib_mem_free (ep);
93         }
94       name_copy = format (0, "%s%c", stat_segment_name, 0);
95       ep = clib_mem_alloc (sizeof (*ep));
96       ep->type = type;
97       ep->value = cm->counters;
98       hash_set_mem (sm->counter_vector_by_name, name_copy, ep);
99
100       /* Reset the client hash table pointer, since it WILL change! */
101       shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR]
102         = sm->counter_vector_by_name;
103
104       /* Warn clients to refresh any pointers they might be holding */
105       shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] = (void *)
106         ((u64) shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] + 1);
107       vlib_stat_segment_unlock ();
108     }
109   ssvm_pop_heap (oldheap);
110 }
111
112 void
113 vlib_stats_register_error_index (u8 * name, u64 index)
114 {
115   stats_main_t *sm = &stats_main;
116   ssvm_private_t *ssvmp = &sm->stat_segment;
117   ssvm_shared_header_t *shared_header;
118   stat_segment_directory_entry_t *ep;
119   hash_pair_t *hp;
120   u8 *name_copy;
121   uword *p;
122
123   ASSERT (ssvmp && ssvmp->sh);
124
125   shared_header = ssvmp->sh;
126
127   vlib_stat_segment_lock ();
128   /* Update hash table. The name must be copied into the segment */
129   hp = hash_get_pair (sm->counter_vector_by_name, name);
130   if (hp)
131     {
132       name_copy = (u8 *) hp->key;
133       ep = (stat_segment_directory_entry_t *) (hp->value[0]);
134       hash_unset_mem (sm->counter_vector_by_name, name);
135       vec_free (name_copy);
136       clib_mem_free (ep);
137     }
138
139   ep = clib_mem_alloc (sizeof (*ep));
140   ep->type = STAT_DIR_TYPE_ERROR_INDEX;
141   ep->value = (void *) index;
142
143   hash_set_mem (sm->counter_vector_by_name, name, ep);
144
145   /* Reset the client hash table pointer, since it WILL change! */
146   shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR] = sm->counter_vector_by_name;
147
148   /* Warn clients to refresh any pointers they might be holding */
149   shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] = (void *)
150     ((u64) shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] + 1);
151   vlib_stat_segment_unlock ();
152 }
153
154 void
155 vlib_stats_pop_heap2 (u64 * counter_vector, u32 thread_index, void *oldheap)
156 {
157   stats_main_t *sm = &stats_main;
158   ssvm_private_t *ssvmp = &sm->stat_segment;
159   ssvm_shared_header_t *shared_header;
160   stat_segment_directory_entry_t *ep;
161   hash_pair_t *hp;
162   u8 *error_vector_name;
163   u8 *name_copy;
164   uword *p;
165
166   ASSERT (ssvmp && ssvmp->sh);
167
168   shared_header = ssvmp->sh;
169
170   vlib_stat_segment_lock ();
171   error_vector_name = format (0, "/err/%d/counter_vector%c", thread_index, 0);
172
173   /* Update hash table. The name must be copied into the segment */
174   hp = hash_get_pair (sm->counter_vector_by_name, error_vector_name);
175   if (hp)
176     {
177       name_copy = (u8 *) hp->key;
178       ep = (stat_segment_directory_entry_t *) (hp->value[0]);
179       hash_unset_mem (sm->counter_vector_by_name, error_vector_name);
180       vec_free (name_copy);
181       clib_mem_free (ep);
182     }
183
184   ep = clib_mem_alloc (sizeof (*ep));
185   ep->type = STAT_DIR_TYPE_VECTOR_POINTER;
186   ep->value = counter_vector;
187
188   hash_set_mem (sm->counter_vector_by_name, error_vector_name, ep);
189
190   /* Reset the client hash table pointer, since it WILL change! */
191   shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR] = sm->counter_vector_by_name;
192
193   /* Warn clients to refresh any pointers they might be holding */
194   shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] = (void *)
195     ((u64) shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] + 1);
196   vlib_stat_segment_unlock ();
197   ssvm_pop_heap (oldheap);
198 }
199
200 clib_error_t *
201 vlib_map_stat_segment_init (void)
202 {
203   stats_main_t *sm = &stats_main;
204   ssvm_private_t *ssvmp = &sm->stat_segment;
205   ssvm_shared_header_t *shared_header;
206   stat_segment_directory_entry_t *ep;
207   f64 *scalar_data;
208   u8 *name;
209   void *oldheap;
210   u32 *lock;
211   int rv;
212   u64 memory_size;
213
214   memory_size = sm->memory_size;
215   if (memory_size == 0)
216     memory_size = STAT_SEGMENT_DEFAULT_SIZE;
217
218   ssvmp->ssvm_size = memory_size;
219   ssvmp->i_am_master = 1;
220   ssvmp->my_pid = getpid ();
221   ssvmp->name = format (0, "/stats%c", 0);
222   ssvmp->requested_va = 0;
223
224   rv = ssvm_master_init (ssvmp, SSVM_SEGMENT_MEMFD);
225
226   if (rv)
227     return clib_error_return (0, "stat segment ssvm init failure");
228   shared_header = ssvmp->sh;
229
230   oldheap = ssvm_push_heap (shared_header);
231
232   /* Set up the name to counter-vector hash table */
233   sm->counter_vector_by_name = hash_create_string (0, sizeof (uword));
234
235   sm->stat_segment_lockp = clib_mem_alloc (sizeof (clib_spinlock_t));
236
237   /* Save the hash table address in the shared segment, for clients */
238   clib_spinlock_init (sm->stat_segment_lockp);
239   shared_header->opaque[STAT_SEGMENT_OPAQUE_LOCK] = sm->stat_segment_lockp;
240   shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] = (void *) 1;
241
242   /* Set up a few scalar stats */
243
244   scalar_data = clib_mem_alloc_aligned (CLIB_CACHE_LINE_BYTES,
245                                         CLIB_CACHE_LINE_BYTES);
246   sm->vector_rate_ptr = (scalar_data + 0);
247   sm->input_rate_ptr = (scalar_data + 1);
248   sm->last_runtime_ptr = (scalar_data + 2);
249   sm->last_runtime_stats_clear_ptr = (scalar_data + 3);
250   sm->heartbeat_ptr = (scalar_data + 4);
251
252   name = format (0, "/sys/vector_rate%c", 0);
253   ep = clib_mem_alloc (sizeof (*ep));
254   ep->type = STAT_DIR_TYPE_SCALAR_POINTER;
255   ep->value = sm->vector_rate_ptr;
256
257   hash_set_mem (sm->counter_vector_by_name, name, ep);
258
259   name = format (0, "/sys/input_rate%c", 0);
260   ep = clib_mem_alloc (sizeof (*ep));
261   ep->type = STAT_DIR_TYPE_SCALAR_POINTER;
262   ep->value = sm->input_rate_ptr;
263
264   hash_set_mem (sm->counter_vector_by_name, name, ep);
265
266   name = format (0, "/sys/last_update%c", 0);
267   ep = clib_mem_alloc (sizeof (*ep));
268   ep->type = STAT_DIR_TYPE_SCALAR_POINTER;
269   ep->value = sm->last_runtime_ptr;
270
271   hash_set_mem (sm->counter_vector_by_name, name, ep);
272
273   name = format (0, "/sys/last_stats_clear%c", 0);
274   ep = clib_mem_alloc (sizeof (*ep));
275   ep->type = STAT_DIR_TYPE_SCALAR_POINTER;
276   ep->value = sm->last_runtime_stats_clear_ptr;
277
278   hash_set_mem (sm->counter_vector_by_name, name, ep);
279
280   name = format (0, "/sys/heartbeat%c", 0);
281   ep = clib_mem_alloc (sizeof (*ep));
282   ep->type = STAT_DIR_TYPE_SCALAR_POINTER;
283   ep->value = sm->heartbeat_ptr;
284
285   hash_set_mem (sm->counter_vector_by_name, name, ep);
286
287
288   /* Publish the hash table */
289   shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR] = sm->counter_vector_by_name;
290
291   ssvm_pop_heap (oldheap);
292
293   return 0;
294 }
295
296 typedef struct
297 {
298   u8 *name;
299   stat_segment_directory_entry_t *dir_entry;
300 } show_stat_segment_t;
301
302 static int
303 name_sort_cmp (void *a1, void *a2)
304 {
305   show_stat_segment_t *n1 = a1;
306   show_stat_segment_t *n2 = a2;
307
308   return strcmp ((char *) n1->name, (char *) n2->name);
309 }
310
311 static u8 *
312 format_stat_dir_entry (u8 * s, va_list * args)
313 {
314   stat_segment_directory_entry_t *ep =
315     va_arg (*args, stat_segment_directory_entry_t *);
316   char *type_name;
317   char *format_string;
318
319   format_string = "%-10s %20llx";
320
321   switch (ep->type)
322     {
323     case STAT_DIR_TYPE_SCALAR_POINTER:
324       type_name = "ScalarPtr";
325       break;
326
327     case STAT_DIR_TYPE_VECTOR_POINTER:
328       type_name = "VectorPtr";
329       break;
330
331     case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
332     case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
333       type_name = "CMainPtr";
334       break;
335
336     case STAT_DIR_TYPE_SERIALIZED_NODES:
337       type_name = "SerNodesPtr";
338       break;
339
340     case STAT_DIR_TYPE_ERROR_INDEX:
341       type_name = "ErrIndex";
342       format_string = "%-10s %20lld";
343       break;
344
345     default:
346       type_name = "illegal!";
347       break;
348     }
349
350   return format (s, format_string, type_name, ep->value);
351 }
352
353 static clib_error_t *
354 show_stat_segment_command_fn (vlib_main_t * vm,
355                               unformat_input_t * input,
356                               vlib_cli_command_t * cmd)
357 {
358   stats_main_t *sm = &stats_main;
359   ssvm_private_t *ssvmp = &sm->stat_segment;
360   ssvm_shared_header_t *shared_header;
361   counter_t *counter;
362   hash_pair_t *p;
363   show_stat_segment_t *show_data = 0;
364   show_stat_segment_t *this;
365   int i;
366
367   int verbose = 0;
368   u8 *s;
369
370   if (unformat (input, "verbose"))
371     verbose = 1;
372
373   vlib_stat_segment_lock ();
374
375   /* *INDENT-OFF* */
376   hash_foreach_pair (p, sm->counter_vector_by_name,
377   ({
378     vec_add2 (show_data, this, 1);
379
380     this->name = (u8 *) (p->key);
381     this->dir_entry = (stat_segment_directory_entry_t *)(p->value[0]);
382   }));
383   /* *INDENT-ON* */
384
385   vlib_stat_segment_unlock ();
386
387   vec_sort_with_function (show_data, name_sort_cmp);
388
389   vlib_cli_output (vm, "%-60s %10s %20s", "Name", "Type", "Value");
390
391   for (i = 0; i < vec_len (show_data); i++)
392     {
393       this = vec_elt_at_index (show_data, i);
394
395       vlib_cli_output (vm, "%-60s %31U",
396                        this->name, format_stat_dir_entry, this->dir_entry);
397     }
398
399   if (verbose)
400     {
401       ASSERT (ssvmp && ssvmp->sh);
402
403       shared_header = ssvmp->sh;
404
405       vlib_cli_output (vm, "%U", format_mheap,
406                        shared_header->heap, 0 /* verbose */ );
407     }
408
409   return 0;
410 }
411
412 /* *INDENT-OFF* */
413 VLIB_CLI_COMMAND (show_stat_segment_command, static) =
414 {
415   .path = "show statistics segment",
416   .short_help = "show statistics segment [verbose]",
417   .function = show_stat_segment_command_fn,
418 };
419 /* *INDENT-ON* */
420
421 static inline void
422 update_serialized_nodes (stats_main_t * sm)
423 {
424   int i;
425   vlib_main_t *vm = vlib_mains[0];
426   ssvm_private_t *ssvmp = &sm->stat_segment;
427   ssvm_shared_header_t *shared_header;
428   void *oldheap;
429   stat_segment_directory_entry_t *ep;
430   hash_pair_t *hp;
431   u8 *name_copy;
432
433   ASSERT (ssvmp && ssvmp->sh);
434
435   vec_reset_length (sm->serialized_nodes);
436
437   shared_header = ssvmp->sh;
438
439   oldheap = ssvm_push_heap (shared_header);
440
441   vlib_stat_segment_lock ();
442   vlib_node_get_nodes (0 /* vm, for barrier sync */ ,
443                        (u32) ~ 0 /* all threads */ ,
444                        1 /* include stats */ ,
445                        0 /* barrier sync */ ,
446                        &sm->node_dups, &sm->stat_vms);
447
448   sm->serialized_nodes = vlib_node_serialize (vm, sm->node_dups,
449                                               sm->serialized_nodes,
450                                               0 /* include nexts */ ,
451                                               1 /* include stats */ );
452
453   hp = hash_get_pair (sm->counter_vector_by_name, "serialized_nodes");
454   if (hp)
455     {
456       name_copy = (u8 *) hp->key;
457       ep = (stat_segment_directory_entry_t *) (hp->value[0]);
458
459       if (ep->value != sm->serialized_nodes)
460         {
461           ep->value = sm->serialized_nodes;
462           /* Warn clients to refresh any pointers they might be holding */
463           shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] = (void *)
464             ((u64) shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] + 1);
465         }
466     }
467   else
468     {
469       name_copy = format (0, "%s%c", "serialized_nodes", 0);
470       ep = clib_mem_alloc (sizeof (*ep));
471       ep->type = STAT_DIR_TYPE_SERIALIZED_NODES;
472       ep->value = sm->serialized_nodes;
473       hash_set_mem (sm->counter_vector_by_name, name_copy, ep);
474
475       /* Reset the client hash table pointer */
476       shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR]
477         = sm->counter_vector_by_name;
478
479       /* Warn clients to refresh any pointers they might be holding */
480       shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] = (void *)
481         ((u64) shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH] + 1);
482     }
483
484   vlib_stat_segment_unlock ();
485   ssvm_pop_heap (oldheap);
486 }
487
488 /*
489  * Called by stats_thread_fn, in stats.c, which runs in a
490  * separate pthread, which won't halt the parade
491  * in single-forwarding-core cases.
492  */
493
494 void
495 do_stat_segment_updates (stats_main_t * sm)
496 {
497   vlib_main_t *vm = vlib_mains[0];
498   f64 vector_rate;
499   u64 input_packets, last_input_packets;
500   f64 dt, now;
501   vlib_main_t *this_vlib_main;
502   int i, start;
503
504   /*
505    * Compute the average vector rate across all workers
506    */
507   vector_rate = 0.0;
508
509   start = vec_len (vlib_mains) > 1 ? 1 : 0;
510
511   for (i = start; i < vec_len (vlib_mains); i++)
512     {
513       this_vlib_main = vlib_mains[i];
514       vector_rate += vlib_last_vector_length_per_node (this_vlib_main);
515     }
516   vector_rate /= (f64) (i - start);
517
518   *sm->vector_rate_ptr = vector_rate / ((f64) (vec_len (vlib_mains) - start));
519
520   /*
521    * Compute the aggregate input rate
522    */
523   now = vlib_time_now (vm);
524   dt = now - sm->last_runtime_ptr[0];
525   input_packets = vnet_get_aggregate_rx_packets ();
526   *sm->input_rate_ptr = (f64) (input_packets - sm->last_input_packets) / dt;
527   sm->last_runtime_ptr[0] = now;
528   sm->last_input_packets = input_packets;
529   sm->last_runtime_stats_clear_ptr[0] =
530     vm->node_main.time_last_runtime_stats_clear;
531
532   if (sm->serialize_nodes)
533     update_serialized_nodes (sm);
534
535   /* Heartbeat, so clients detect we're still here */
536   (*sm->heartbeat_ptr)++;
537 }
538
539 static clib_error_t *
540 statseg_config (vlib_main_t * vm, unformat_input_t * input)
541 {
542   stats_main_t *sm = &stats_main;
543   uword ms;
544
545   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
546     {
547       if (unformat (input, "size %U", unformat_memory_size, &sm->memory_size))
548         ;
549       else if (unformat (input, "serialize-nodes on"))
550         sm->serialize_nodes = 1;
551       else if (unformat (input, "serialize-nodes off"))
552         sm->serialize_nodes = 0;
553       else
554         return clib_error_return (0, "unknown input `%U'",
555                                   format_unformat_error, input);
556     }
557
558   return 0;
559 }
560
561 VLIB_EARLY_CONFIG_FUNCTION (statseg_config, "statseg");
562
563 /*
564  * fd.io coding-style-patch-verification: ON
565  *
566  * Local Variables:
567  * eval: (c-set-style "gnu")
568  * End:
569  */