summaryrefslogtreecommitdiffstats
path: root/src/vlib/stats
diff options
context:
space:
mode:
Diffstat (limited to 'src/vlib/stats')
-rw-r--r--src/vlib/stats/cli.c125
-rw-r--r--src/vlib/stats/collector.c192
-rw-r--r--src/vlib/stats/format.c24
-rw-r--r--src/vlib/stats/init.c246
-rw-r--r--src/vlib/stats/provider_mem.c68
-rw-r--r--src/vlib/stats/shared.h52
-rw-r--r--src/vlib/stats/stats.c514
-rw-r--r--src/vlib/stats/stats.h160
8 files changed, 1381 insertions, 0 deletions
diff --git a/src/vlib/stats/cli.c b/src/vlib/stats/cli.c
new file mode 100644
index 00000000000..4d7026b5995
--- /dev/null
+++ b/src/vlib/stats/cli.c
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2022 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vlib/stats/stats.h>
+
+static int
+name_sort_cmp (void *a1, void *a2)
+{
+ vlib_stats_entry_t *n1 = a1;
+ vlib_stats_entry_t *n2 = a2;
+
+ return strcmp ((char *) n1->name, (char *) n2->name);
+}
+
+static u8 *
+format_stat_dir_entry (u8 *s, va_list *args)
+{
+ vlib_stats_entry_t *ep = va_arg (*args, vlib_stats_entry_t *);
+ char *type_name;
+ char *format_string;
+
+ format_string = "%-74s %-10s %10lld";
+
+ switch (ep->type)
+ {
+ case STAT_DIR_TYPE_SCALAR_INDEX:
+ type_name = "ScalarPtr";
+ break;
+
+ case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
+ case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
+ type_name = "CMainPtr";
+ break;
+
+ case STAT_DIR_TYPE_ERROR_INDEX:
+ type_name = "ErrIndex";
+ break;
+
+ case STAT_DIR_TYPE_NAME_VECTOR:
+ type_name = "NameVector";
+ break;
+
+ case STAT_DIR_TYPE_EMPTY:
+ type_name = "empty";
+ break;
+
+ case STAT_DIR_TYPE_SYMLINK:
+ type_name = "Symlink";
+ break;
+
+ default:
+ type_name = "illegal!";
+ break;
+ }
+
+ return format (s, format_string, ep->name, type_name, 0);
+}
+static clib_error_t *
+show_stat_segment_command_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_entry_t *show_data;
+ int i;
+
+ int verbose = 0;
+
+ if (unformat (input, "verbose"))
+ verbose = 1;
+
+ /* Lock even as reader, as this command doesn't handle epoch changes */
+ vlib_stats_segment_lock ();
+ show_data = vec_dup (sm->directory_vector);
+ vlib_stats_segment_unlock ();
+
+ vec_sort_with_function (show_data, name_sort_cmp);
+
+ vlib_cli_output (vm, "%-74s %10s %10s", "Name", "Type", "Value");
+
+ for (i = 0; i < vec_len (show_data); i++)
+ {
+ vlib_stats_entry_t *ep = vec_elt_at_index (show_data, i);
+
+ if (ep->type == STAT_DIR_TYPE_EMPTY)
+ continue;
+
+ vlib_cli_output (vm, "%-100U", format_stat_dir_entry,
+ vec_elt_at_index (show_data, i));
+ }
+
+ if (verbose)
+ {
+ ASSERT (sm->heap);
+ vlib_cli_output (vm, "%U", format_clib_mem_heap, sm->heap,
+ 0 /* verbose */);
+ }
+
+ return 0;
+}
+
+static clib_error_t *
+show_stat_segment_hash_command_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ char *name;
+ u32 i;
+ hash_foreach_mem (name, i, sm->directory_vector_by_name,
+ ({ vlib_cli_output (vm, "%d: %s\n", i, name); }));
+ return 0;
+}
+
+VLIB_CLI_COMMAND (show_stat_segment_hash_command, static) = {
+ .path = "show statistics hash",
+ .short_help = "show statistics hash",
+ .function = show_stat_segment_hash_command_fn,
+};
+
+VLIB_CLI_COMMAND (show_stat_segment_command, static) = {
+ .path = "show statistics segment",
+ .short_help = "show statistics segment [verbose]",
+ .function = show_stat_segment_command_fn,
+};
diff --git a/src/vlib/stats/collector.c b/src/vlib/stats/collector.c
new file mode 100644
index 00000000000..cc348bbf3ad
--- /dev/null
+++ b/src/vlib/stats/collector.c
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2022 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vlib/unix/unix.h>
+#include <vlib/stats/stats.h>
+
+static void
+stat_validate_counter_vector2 (vlib_stats_entry_t *ep, u32 max1, u32 max2)
+{
+ counter_t **counters = ep->data;
+ int i;
+ vec_validate_aligned (counters, max1, CLIB_CACHE_LINE_BYTES);
+ for (i = 0; i <= max1; i++)
+ vec_validate_aligned (counters[i], max2, CLIB_CACHE_LINE_BYTES);
+
+ ep->data = counters;
+}
+
+static void
+stat_validate_counter_vector (vlib_stats_entry_t *ep, u32 max)
+{
+ vlib_thread_main_t *tm = vlib_get_thread_main ();
+ ASSERT (tm->n_vlib_mains > 0);
+ stat_validate_counter_vector2 (ep, tm->n_vlib_mains, max);
+}
+
+static inline void
+update_node_counters (vlib_stats_segment_t *sm)
+{
+ vlib_main_t **stat_vms = 0;
+ vlib_node_t ***node_dups = 0;
+ int i, j;
+ static u32 no_max_nodes = 0;
+
+ vlib_node_get_nodes (0 /* vm, for barrier sync */,
+ (u32) ~0 /* all threads */, 1 /* include stats */,
+ 0 /* barrier sync */, &node_dups, &stat_vms);
+
+ u32 l = vec_len (node_dups[0]);
+ u8 *symlink_name = 0;
+
+ /*
+ * Extend performance nodes if necessary
+ */
+ if (l > no_max_nodes)
+ {
+ void *oldheap = clib_mem_set_heap (sm->heap);
+ vlib_stats_segment_lock ();
+
+ stat_validate_counter_vector (
+ &sm->directory_vector[STAT_COUNTER_NODE_CLOCKS], l - 1);
+ stat_validate_counter_vector (
+ &sm->directory_vector[STAT_COUNTER_NODE_VECTORS], l - 1);
+ stat_validate_counter_vector (
+ &sm->directory_vector[STAT_COUNTER_NODE_CALLS], l - 1);
+ stat_validate_counter_vector (
+ &sm->directory_vector[STAT_COUNTER_NODE_SUSPENDS], l - 1);
+
+ vec_validate (sm->nodes, l - 1);
+ vlib_stats_entry_t *ep;
+ ep = &sm->directory_vector[STAT_COUNTER_NODE_NAMES];
+ ep->data = sm->nodes;
+
+ /* Update names dictionary */
+ vlib_node_t **nodes = node_dups[0];
+ int i;
+ for (i = 0; i < vec_len (nodes); i++)
+ {
+ vlib_node_t *n = nodes[i];
+ u8 *s = format (0, "%v%c", n->name, 0);
+ if (sm->nodes[n->index])
+ vec_free (sm->nodes[n->index]);
+ sm->nodes[n->index] = s;
+
+ oldheap = clib_mem_set_heap (oldheap);
+#define _(E, t, name, p) \
+ vlib_stats_add_symlink (STAT_COUNTER_##E, n->index, "/nodes/%U/" #name, \
+ format_vlib_stats_symlink, s);
+ foreach_stat_segment_node_counter_name
+#undef _
+ oldheap = clib_mem_set_heap (oldheap);
+ }
+
+ vlib_stats_segment_unlock ();
+ clib_mem_set_heap (oldheap);
+ no_max_nodes = l;
+ }
+
+ for (j = 0; j < vec_len (node_dups); j++)
+ {
+ vlib_node_t **nodes = node_dups[j];
+
+ for (i = 0; i < vec_len (nodes); i++)
+ {
+ counter_t **counters;
+ counter_t *c;
+ vlib_node_t *n = nodes[i];
+
+ if (j == 0)
+ {
+ if (strncmp ((char *) sm->nodes[n->index], (char *) n->name,
+ strlen ((char *) sm->nodes[n->index])))
+ {
+ u32 vector_index;
+ void *oldheap = clib_mem_set_heap (sm->heap);
+ vlib_stats_segment_lock ();
+ u8 *s = format (0, "%v%c", n->name, 0);
+ clib_mem_set_heap (oldheap);
+#define _(E, t, name, p) \
+ vec_reset_length (symlink_name); \
+ symlink_name = format (symlink_name, "/nodes/%U/" #name, \
+ format_vlib_stats_symlink, sm->nodes[n->index]); \
+ vector_index = vlib_stats_find_entry_index ("%v", symlink_name); \
+ ASSERT (vector_index != -1); \
+ vlib_stats_rename_symlink (vector_index, "/nodes/%U/" #name, \
+ format_vlib_stats_symlink, s);
+ foreach_stat_segment_node_counter_name
+#undef _
+ vec_free (symlink_name);
+ clib_mem_set_heap (sm->heap);
+ vec_free (sm->nodes[n->index]);
+ sm->nodes[n->index] = s;
+ vlib_stats_segment_unlock ();
+ clib_mem_set_heap (oldheap);
+ }
+ }
+
+ counters = sm->directory_vector[STAT_COUNTER_NODE_CLOCKS].data;
+ c = counters[j];
+ c[n->index] = n->stats_total.clocks - n->stats_last_clear.clocks;
+
+ counters = sm->directory_vector[STAT_COUNTER_NODE_VECTORS].data;
+ c = counters[j];
+ c[n->index] = n->stats_total.vectors - n->stats_last_clear.vectors;
+
+ counters = sm->directory_vector[STAT_COUNTER_NODE_CALLS].data;
+ c = counters[j];
+ c[n->index] = n->stats_total.calls - n->stats_last_clear.calls;
+
+ counters = sm->directory_vector[STAT_COUNTER_NODE_SUSPENDS].data;
+ c = counters[j];
+ c[n->index] = n->stats_total.suspends - n->stats_last_clear.suspends;
+ }
+ vec_free (node_dups[j]);
+ }
+ vec_free (node_dups);
+ vec_free (stat_vms);
+}
+
+static void
+do_stat_segment_updates (vlib_main_t *vm, vlib_stats_segment_t *sm)
+{
+ if (sm->node_counters_enabled)
+ update_node_counters (sm);
+
+ vlib_stats_collector_t *c;
+ pool_foreach (c, sm->collectors)
+ {
+ vlib_stats_collector_data_t data = {
+ .entry_index = c->entry_index,
+ .vector_index = c->vector_index,
+ .private_data = c->private_data,
+ .entry = sm->directory_vector + c->entry_index,
+ };
+ c->fn (&data);
+ }
+
+ /* Heartbeat, so clients detect we're still here */
+ sm->directory_vector[STAT_COUNTER_HEARTBEAT].value++;
+}
+
+static uword
+stat_segment_collector_process (vlib_main_t *vm, vlib_node_runtime_t *rt,
+ vlib_frame_t *f)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+
+ while (1)
+ {
+ do_stat_segment_updates (vm, sm);
+ vlib_process_suspend (vm, sm->update_interval);
+ }
+ return 0; /* or not */
+}
+
+VLIB_REGISTER_NODE (stat_segment_collector, static) = {
+ .function = stat_segment_collector_process,
+ .name = "statseg-collector-process",
+ .type = VLIB_NODE_TYPE_PROCESS,
+};
diff --git a/src/vlib/stats/format.c b/src/vlib/stats/format.c
new file mode 100644
index 00000000000..419ff6b4657
--- /dev/null
+++ b/src/vlib/stats/format.c
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2022 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vlib/unix/unix.h>
+#include <vlib/stats/stats.h>
+
+u8 *
+format_vlib_stats_symlink (u8 *s, va_list *args)
+{
+ char *input = va_arg (*args, char *);
+ char *modified_input = vec_dup (input);
+ int i;
+ u8 *result;
+
+ for (i = 0; i < strlen (modified_input); i++)
+ if (modified_input[i] == '/')
+ modified_input[i] = '_';
+
+ result = format (s, "%s", modified_input);
+ vec_free (modified_input);
+ return result;
+}
diff --git a/src/vlib/stats/init.c b/src/vlib/stats/init.c
new file mode 100644
index 00000000000..d81916a288d
--- /dev/null
+++ b/src/vlib/stats/init.c
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2022 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vlib/unix/unix.h>
+#include <vlib/stats/stats.h>
+
+#define STAT_SEGMENT_SOCKET_FILENAME "stats.sock"
+
+static void
+vector_rate_collector_fn (vlib_stats_collector_data_t *d)
+{
+ vlib_main_t *this_vlib_main;
+ counter_t **counters = d->entry->data;
+ counter_t *cb = counters[0];
+ f64 vector_rate = 0.0;
+ u32 i, n_threads = vlib_get_n_threads ();
+
+ vlib_stats_validate_counter_vector (d->entry_index, n_threads - 1);
+
+ for (i = 0; i < n_threads; i++)
+ {
+ f64 this_vector_rate;
+ this_vlib_main = vlib_get_main_by_index (i);
+
+ this_vector_rate = vlib_internal_node_vector_rate (this_vlib_main);
+ vlib_clear_internal_node_vector_rate (this_vlib_main);
+
+ cb[i] = this_vector_rate;
+ vector_rate += this_vector_rate;
+ }
+
+ /* And set the system average rate */
+ vector_rate /= (f64) (i > 1 ? i - 1 : 1);
+ vlib_stats_set_gauge (d->private_data, vector_rate);
+}
+
+clib_error_t *
+vlib_stats_init (vlib_main_t *vm)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_shared_header_t *shared_header;
+ vlib_stats_collector_reg_t reg = {};
+
+ void *oldheap;
+ uword memory_size, sys_page_sz;
+ int mfd;
+ char *mem_name = "stat segment";
+ void *heap, *memaddr;
+
+ memory_size = sm->memory_size;
+ if (memory_size == 0)
+ memory_size = STAT_SEGMENT_DEFAULT_SIZE;
+
+ if (sm->log2_page_sz == CLIB_MEM_PAGE_SZ_UNKNOWN)
+ sm->log2_page_sz = CLIB_MEM_PAGE_SZ_DEFAULT;
+
+ mfd = clib_mem_vm_create_fd (sm->log2_page_sz, mem_name);
+
+ if (mfd == -1)
+ return clib_error_return (0, "stat segment memory fd failure: %U",
+ format_clib_error, clib_mem_get_last_error ());
+ /* Set size */
+ if ((ftruncate (mfd, memory_size)) == -1)
+ {
+ close (mfd);
+ return clib_error_return (0, "stat segment ftruncate failure");
+ }
+
+ memaddr = clib_mem_vm_map_shared (0, memory_size, mfd, 0, mem_name);
+
+ if (memaddr == CLIB_MEM_VM_MAP_FAILED)
+ return clib_error_return (0, "stat segment mmap failure");
+
+ sys_page_sz = clib_mem_get_page_size ();
+
+ heap =
+ clib_mem_create_heap (((u8 *) memaddr) + sys_page_sz,
+ memory_size - sys_page_sz, 1 /* locked */, mem_name);
+ sm->heap = heap;
+ sm->memfd = mfd;
+
+ sm->directory_vector_by_name = hash_create_string (0, sizeof (uword));
+ sm->shared_header = shared_header = memaddr;
+
+ shared_header->version = STAT_SEGMENT_VERSION;
+ shared_header->base = memaddr;
+
+ sm->stat_segment_lockp = clib_mem_alloc (sizeof (clib_spinlock_t));
+ clib_spinlock_init (sm->stat_segment_lockp);
+
+ oldheap = clib_mem_set_heap (sm->heap);
+
+ /* Set up the name to counter-vector hash table */
+ sm->directory_vector = 0;
+
+ shared_header->epoch = 1;
+
+ /* Scalar stats and node counters */
+ vec_validate (sm->directory_vector, STAT_COUNTERS - 1);
+#define _(E, t, n, p) \
+ strcpy (sm->directory_vector[STAT_COUNTER_##E].name, p "/" #n); \
+ sm->directory_vector[STAT_COUNTER_##E].type = STAT_DIR_TYPE_##t;
+ foreach_stat_segment_counter_name
+#undef _
+ /* Save the vector in the shared segment, for clients */
+ shared_header->directory_vector = sm->directory_vector;
+
+ clib_mem_set_heap (oldheap);
+
+ vlib_stats_register_mem_heap (heap);
+
+ reg.collect_fn = vector_rate_collector_fn;
+ reg.private_data = vlib_stats_add_gauge ("/sys/vector_rate");
+ reg.entry_index =
+ vlib_stats_add_counter_vector ("/sys/vector_rate_per_worker");
+ vlib_stats_register_collector_fn (&reg);
+ vlib_stats_validate_counter_vector (reg.entry_index, vlib_get_n_threads ());
+
+ return 0;
+}
+
+static clib_error_t *
+statseg_config (vlib_main_t *vm, unformat_input_t *input)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ sm->update_interval = 10.0;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "socket-name %s", &sm->socket_name))
+ ;
+ /* DEPRECATE: default (does nothing) */
+ else if (unformat (input, "default"))
+ ;
+ else if (unformat (input, "size %U", unformat_memory_size,
+ &sm->memory_size))
+ ;
+ else if (unformat (input, "page-size %U", unformat_log2_page_size,
+ &sm->log2_page_sz))
+ ;
+ else if (unformat (input, "per-node-counters on"))
+ sm->node_counters_enabled = 1;
+ else if (unformat (input, "per-node-counters off"))
+ sm->node_counters_enabled = 0;
+ else if (unformat (input, "update-interval %f", &sm->update_interval))
+ ;
+ else
+ return clib_error_return (0, "unknown input `%U'",
+ format_unformat_error, input);
+ }
+
+ /*
+ * NULL-terminate socket name string
+ * clib_socket_init()->socket_config() use C str*
+ */
+ if (vec_len (sm->socket_name))
+ vec_terminate_c_string (sm->socket_name);
+
+ return 0;
+}
+
+VLIB_EARLY_CONFIG_FUNCTION (statseg_config, "statseg");
+
+/*
+ * Accept connection on the socket and exchange the fd for the shared
+ * memory segment.
+ */
+static clib_error_t *
+stats_socket_accept_ready (clib_file_t *uf)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ clib_error_t *err;
+ clib_socket_t client = { 0 };
+
+ err = clib_socket_accept (sm->socket, &client);
+ if (err)
+ {
+ clib_error_report (err);
+ return err;
+ }
+
+ /* Send the fd across and close */
+ err = clib_socket_sendmsg (&client, 0, 0, &sm->memfd, 1);
+ if (err)
+ clib_error_report (err);
+ clib_socket_close (&client);
+
+ return 0;
+}
+
+static clib_error_t *
+stats_segment_socket_init (void)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ clib_error_t *error;
+ clib_socket_t *s = clib_mem_alloc (sizeof (clib_socket_t));
+
+ memset (s, 0, sizeof (clib_socket_t));
+ s->config = (char *) sm->socket_name;
+ s->flags = CLIB_SOCKET_F_IS_SERVER | CLIB_SOCKET_F_SEQPACKET |
+ CLIB_SOCKET_F_ALLOW_GROUP_WRITE | CLIB_SOCKET_F_PASSCRED;
+
+ if ((error = clib_socket_init (s)))
+ return error;
+
+ clib_file_t template = { 0 };
+ template.read_function = stats_socket_accept_ready;
+ template.file_descriptor = s->fd;
+ template.description = format (0, "stats segment listener %s", s->config);
+ clib_file_add (&file_main, &template);
+
+ sm->socket = s;
+
+ return 0;
+}
+
+static clib_error_t *
+stats_segment_socket_exit (vlib_main_t *vm)
+{
+ /*
+ * cleanup the listener socket on exit.
+ */
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ unlink ((char *) sm->socket_name);
+ return 0;
+}
+
+VLIB_MAIN_LOOP_EXIT_FUNCTION (stats_segment_socket_exit);
+
+static clib_error_t *
+statseg_init (vlib_main_t *vm)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+
+ /* set default socket file name when statseg config stanza is empty. */
+ if (!vec_len (sm->socket_name))
+ sm->socket_name = format (0, "%s/%s%c", vlib_unix_get_runtime_dir (),
+ STAT_SEGMENT_SOCKET_FILENAME, 0);
+ return stats_segment_socket_init ();
+}
+
+VLIB_INIT_FUNCTION (statseg_init) = {
+ .runs_after = VLIB_INITS ("unix_input_init", "linux_epoll_input_init"),
+};
diff --git a/src/vlib/stats/provider_mem.c b/src/vlib/stats/provider_mem.c
new file mode 100644
index 00000000000..d52ea4aa0e5
--- /dev/null
+++ b/src/vlib/stats/provider_mem.c
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2022 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vlib/unix/unix.h>
+#include <vlib/stats/stats.h>
+
+static clib_mem_heap_t **memory_heaps_vec;
+
+enum
+{
+ STAT_MEM_TOTAL = 0,
+ STAT_MEM_USED,
+ STAT_MEM_FREE,
+ STAT_MEM_USED_MMAP,
+ STAT_MEM_TOTAL_ALLOC,
+ STAT_MEM_FREE_CHUNKS,
+ STAT_MEM_RELEASABLE,
+} stat_mem_usage_e;
+
+/*
+ * Called from the stats periodic process to update memory counters.
+ */
+static void
+stat_provider_mem_usage_update_fn (vlib_stats_collector_data_t *d)
+{
+ clib_mem_usage_t usage;
+ clib_mem_heap_t *heap;
+ counter_t **counters = d->entry->data;
+ counter_t *cb;
+
+ heap = vec_elt (memory_heaps_vec, d->private_data);
+ clib_mem_get_heap_usage (heap, &usage);
+ cb = counters[0];
+ cb[STAT_MEM_TOTAL] = usage.bytes_total;
+ cb[STAT_MEM_USED] = usage.bytes_used;
+ cb[STAT_MEM_FREE] = usage.bytes_free;
+ cb[STAT_MEM_USED_MMAP] = usage.bytes_used_mmap;
+ cb[STAT_MEM_TOTAL_ALLOC] = usage.bytes_max;
+ cb[STAT_MEM_FREE_CHUNKS] = usage.bytes_free_reclaimed;
+ cb[STAT_MEM_RELEASABLE] = usage.bytes_overhead;
+}
+
+/*
+ * Provide memory heap counters.
+ * Two dimensional array of heap index and per-heap gauges.
+ */
+void
+vlib_stats_register_mem_heap (clib_mem_heap_t *heap)
+{
+ vlib_stats_collector_reg_t r = {};
+ u32 idx;
+
+ vec_add1 (memory_heaps_vec, heap);
+
+ r.entry_index = idx = vlib_stats_add_counter_vector ("/mem/%s", heap->name);
+ vlib_stats_validate_counter_vector (idx, STAT_MEM_RELEASABLE);
+
+ /* Create symlink */
+ vlib_stats_add_symlink (idx, STAT_MEM_TOTAL, "/mem/%s/used", heap->name);
+ vlib_stats_add_symlink (idx, STAT_MEM_USED, "/mem/%s/total", heap->name);
+ vlib_stats_add_symlink (idx, STAT_MEM_FREE, "/mem/%s/free", heap->name);
+
+ r.private_data = vec_len (memory_heaps_vec) - 1;
+ r.collect_fn = stat_provider_mem_usage_update_fn;
+ vlib_stats_register_collector_fn (&r);
+}
diff --git a/src/vlib/stats/shared.h b/src/vlib/stats/shared.h
new file mode 100644
index 00000000000..d2d5d49cc54
--- /dev/null
+++ b/src/vlib/stats/shared.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2022 Cisco Systems, Inc.
+ */
+
+#ifndef included_stat_segment_shared_h
+#define included_stat_segment_shared_h
+
+typedef enum
+{
+ STAT_DIR_TYPE_ILLEGAL = 0,
+ STAT_DIR_TYPE_SCALAR_INDEX,
+ STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE,
+ STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED,
+ STAT_DIR_TYPE_ERROR_INDEX,
+ STAT_DIR_TYPE_NAME_VECTOR,
+ STAT_DIR_TYPE_EMPTY,
+ STAT_DIR_TYPE_SYMLINK,
+} stat_directory_type_t;
+
+typedef struct
+{
+ stat_directory_type_t type;
+ union
+ {
+ struct
+ {
+ uint32_t index1;
+ uint32_t index2;
+ };
+ uint64_t index;
+ uint64_t value;
+ void *data;
+ uint8_t **string_vector;
+ };
+#define VLIB_STATS_MAX_NAME_SZ 128
+ char name[VLIB_STATS_MAX_NAME_SZ];
+} vlib_stats_entry_t;
+
+/*
+ * Shared header first in the shared memory segment.
+ */
+typedef struct
+{
+ uint64_t version;
+ void *base;
+ volatile uint64_t epoch;
+ volatile uint64_t in_progress;
+ volatile vlib_stats_entry_t *directory_vector;
+ volatile uint64_t **error_vector;
+} vlib_stats_shared_header_t;
+
+#endif /* included_stat_segment_shared_h */
diff --git a/src/vlib/stats/stats.c b/src/vlib/stats/stats.c
new file mode 100644
index 00000000000..19edaf0866d
--- /dev/null
+++ b/src/vlib/stats/stats.c
@@ -0,0 +1,514 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2022 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vlib/stats/stats.h>
+
+vlib_stats_main_t vlib_stats_main;
+
+/*
+ * Used only by VPP writers
+ */
+
+void
+vlib_stats_segment_lock (void)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ clib_spinlock_lock (sm->stat_segment_lockp);
+ sm->shared_header->in_progress = 1;
+}
+
+void
+vlib_stats_segment_unlock (void)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ sm->shared_header->epoch++;
+ sm->shared_header->in_progress = 0;
+ clib_spinlock_unlock (sm->stat_segment_lockp);
+}
+
+/*
+ * Change heap to the stats shared memory segment
+ */
+void *
+vlib_stats_set_heap ()
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+
+ ASSERT (sm && sm->shared_header);
+ return clib_mem_set_heap (sm->heap);
+}
+
+u32
+vlib_stats_find_entry_index (char *fmt, ...)
+{
+ u8 *name;
+ va_list va;
+
+ va_start (va, fmt);
+ name = va_format (0, fmt, &va);
+ va_end (va);
+ vec_add1 (name, 0);
+
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ hash_pair_t *hp = hash_get_pair (sm->directory_vector_by_name, name);
+ vec_free (name);
+ return hp ? hp->value[0] : STAT_SEGMENT_INDEX_INVALID;
+}
+
+static void
+hash_set_str_key_alloc (uword **h, const char *key, uword v)
+{
+ int size = strlen (key) + 1;
+ void *copy = clib_mem_alloc (size);
+ clib_memcpy_fast (copy, key, size);
+ hash_set_mem (*h, copy, v);
+}
+
+static void
+hash_unset_str_key_free (uword **h, const char *key)
+{
+ hash_pair_t *hp = hash_get_pair_mem (*h, key);
+ if (hp)
+ {
+ void *_k = uword_to_pointer (hp->key, void *);
+ hash_unset_mem (*h, _k);
+ clib_mem_free (_k);
+ }
+}
+
+u32
+vlib_stats_create_counter (vlib_stats_entry_t *e)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ void *oldheap;
+ u32 index = ~0;
+ int i;
+
+ oldheap = clib_mem_set_heap (sm->heap);
+ vec_foreach_index_backwards (i, sm->directory_vector)
+ if (sm->directory_vector[i].type == STAT_DIR_TYPE_EMPTY)
+ {
+ index = i;
+ break;
+ }
+
+ index = index == ~0 ? vec_len (sm->directory_vector) : index;
+
+ vec_validate (sm->directory_vector, index);
+ sm->directory_vector[index] = *e;
+
+ clib_mem_set_heap (oldheap);
+ hash_set_str_key_alloc (&sm->directory_vector_by_name, e->name, index);
+
+ return index;
+}
+
+void
+vlib_stats_remove_entry (u32 entry_index)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_entry_t *e = vlib_stats_get_entry (sm, entry_index);
+ void *oldheap;
+ counter_t **c;
+ u32 i;
+
+ if (entry_index >= vec_len (sm->directory_vector))
+ return;
+
+ oldheap = clib_mem_set_heap (sm->heap);
+
+ switch (e->type)
+ {
+ case STAT_DIR_TYPE_NAME_VECTOR:
+ for (i = 0; i < vec_len (e->string_vector); i++)
+ vec_free (e->string_vector[i]);
+ vec_free (e->string_vector);
+ break;
+
+ case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
+ c = e->data;
+ e->data = 0;
+ for (i = 0; i < vec_len (c); i++)
+ vec_free (c[i]);
+ vec_free (c);
+ break;
+
+ case STAT_DIR_TYPE_SCALAR_INDEX:
+ case STAT_DIR_TYPE_SYMLINK:
+ break;
+ default:
+ ASSERT (0);
+ }
+
+ clib_mem_set_heap (oldheap);
+ hash_unset_str_key_free (&sm->directory_vector_by_name, e->name);
+
+ memset (e, 0, sizeof (*e));
+ e->type = STAT_DIR_TYPE_EMPTY;
+}
+
+static void
+vlib_stats_set_entry_name (vlib_stats_entry_t *e, char *s)
+{
+ u32 i, len = VLIB_STATS_MAX_NAME_SZ - 1;
+
+ for (i = 0; i < len; i++)
+ {
+ e->name[i] = s[i];
+ if (s[i] == 0)
+ return;
+ }
+ ASSERT (i < VLIB_STATS_MAX_NAME_SZ - 1);
+ s[i] = 0;
+}
+
+void
+vlib_stats_update_counter (void *cm_arg, u32 cindex,
+ stat_directory_type_t type)
+{
+ vlib_simple_counter_main_t *cm = (vlib_simple_counter_main_t *) cm_arg;
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_shared_header_t *shared_header = sm->shared_header;
+ char *stat_segment_name;
+ vlib_stats_entry_t e = { 0 };
+
+ /* Not all counters have names / hash-table entries */
+ if (!cm->name && !cm->stat_segment_name)
+ return;
+
+ ASSERT (shared_header);
+
+ vlib_stats_segment_lock ();
+
+ /* Lookup hash-table is on the main heap */
+ stat_segment_name = cm->stat_segment_name ? cm->stat_segment_name : cm->name;
+
+ u32 vector_index = vlib_stats_find_entry_index ("%s", stat_segment_name);
+
+ /* Update the vector */
+ if (vector_index == STAT_SEGMENT_INDEX_INVALID)
+ {
+ vlib_stats_set_entry_name (&e, stat_segment_name);
+ e.type = type;
+ vector_index = vlib_stats_create_counter (&e);
+ }
+
+ vlib_stats_entry_t *ep = &sm->directory_vector[vector_index];
+ ep->data = cm->counters;
+
+ /* Reset the client hash table pointer, since it WILL change! */
+ shared_header->directory_vector = sm->directory_vector;
+
+ vlib_stats_segment_unlock ();
+}
+
+void
+vlib_stats_register_error_index (u64 *em_vec, u64 index, char *fmt, ...)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_shared_header_t *shared_header = sm->shared_header;
+ vlib_stats_entry_t e = {};
+ va_list va;
+ u8 *name;
+
+ va_start (va, fmt);
+ name = va_format (0, fmt, &va);
+ va_end (va);
+
+ ASSERT (shared_header);
+
+ vlib_stats_segment_lock ();
+ u32 vector_index = vlib_stats_find_entry_index ("%v", name);
+
+ if (vector_index == STAT_SEGMENT_INDEX_INVALID)
+ {
+ vec_add1 (name, 0);
+ vlib_stats_set_entry_name (&e, (char *) name);
+ e.type = STAT_DIR_TYPE_ERROR_INDEX;
+ e.index = index;
+ vector_index = vlib_stats_create_counter (&e);
+
+ /* Warn clients to refresh any pointers they might be holding */
+ shared_header->directory_vector = sm->directory_vector;
+ }
+
+ vlib_stats_segment_unlock ();
+ vec_free (name);
+}
+
+void
+vlib_stats_update_error_vector (u64 *error_vector, u32 thread_index, int lock)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_shared_header_t *shared_header = sm->shared_header;
+ void *oldheap = clib_mem_set_heap (sm->heap);
+
+ ASSERT (shared_header);
+
+ if (lock)
+ vlib_stats_segment_lock ();
+
+ /* Reset the client hash table pointer, since it WILL change! */
+ vec_validate (sm->error_vector, thread_index);
+ sm->error_vector[thread_index] = error_vector;
+
+ shared_header->error_vector = sm->error_vector;
+ shared_header->directory_vector = sm->directory_vector;
+
+ if (lock)
+ vlib_stats_segment_unlock ();
+ clib_mem_set_heap (oldheap);
+}
+
+void
+vlib_stats_delete_cm (void *cm_arg)
+{
+ vlib_simple_counter_main_t *cm = (vlib_simple_counter_main_t *) cm_arg;
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_entry_t *e;
+
+ /* Not all counters have names / hash-table entries */
+ if (!cm->name && !cm->stat_segment_name)
+ return;
+
+ vlib_stats_segment_lock ();
+
+ /* Lookup hash-table is on the main heap */
+ char *stat_segment_name =
+ cm->stat_segment_name ? cm->stat_segment_name : cm->name;
+ u32 index = vlib_stats_find_entry_index ("%s", stat_segment_name);
+
+ e = &sm->directory_vector[index];
+
+ hash_unset_str_key_free (&sm->directory_vector_by_name, e->name);
+
+ memset (e, 0, sizeof (*e));
+ e->type = STAT_DIR_TYPE_EMPTY;
+
+ vlib_stats_segment_unlock ();
+}
+
+static u32
+vlib_stats_new_entry_internal (stat_directory_type_t t, u8 *name)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_shared_header_t *shared_header = sm->shared_header;
+ vlib_stats_entry_t e = { .type = t };
+
+ ASSERT (shared_header);
+
+ u32 vector_index = vlib_stats_find_entry_index ("%v", name);
+ if (vector_index != STAT_SEGMENT_INDEX_INVALID) /* Already registered */
+ {
+ vector_index = ~0;
+ goto done;
+ }
+
+ vec_add1 (name, 0);
+ vlib_stats_set_entry_name (&e, (char *) name);
+
+ vlib_stats_segment_lock ();
+ vector_index = vlib_stats_create_counter (&e);
+
+ shared_header->directory_vector = sm->directory_vector;
+
+ vlib_stats_segment_unlock ();
+
+done:
+ vec_free (name);
+ return vector_index;
+}
+
+u32
+vlib_stats_add_gauge (char *fmt, ...)
+{
+ va_list va;
+ u8 *name;
+
+ va_start (va, fmt);
+ name = va_format (0, fmt, &va);
+ va_end (va);
+ return vlib_stats_new_entry_internal (STAT_DIR_TYPE_SCALAR_INDEX, name);
+}
+
+void
+vlib_stats_set_gauge (u32 index, u64 value)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+
+ ASSERT (index < vec_len (sm->directory_vector));
+ sm->directory_vector[index].value = value;
+}
+
+u32
+vlib_stats_add_timestamp (char *fmt, ...)
+{
+ va_list va;
+ u8 *name;
+
+ va_start (va, fmt);
+ name = va_format (0, fmt, &va);
+ va_end (va);
+ return vlib_stats_new_entry_internal (STAT_DIR_TYPE_SCALAR_INDEX, name);
+}
+
+void
+vlib_stats_set_timestamp (u32 entry_index, f64 value)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+
+ ASSERT (entry_index < vec_len (sm->directory_vector));
+ sm->directory_vector[entry_index].value = value;
+}
+
+u32
+vlib_stats_add_string_vector (char *fmt, ...)
+{
+ va_list va;
+ u8 *name;
+
+ va_start (va, fmt);
+ name = va_format (0, fmt, &va);
+ va_end (va);
+ return vlib_stats_new_entry_internal (STAT_DIR_TYPE_NAME_VECTOR, name);
+}
+
+void
+vlib_stats_set_string_vector (u32 entry_index, u32 vector_index, char *fmt,
+ ...)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_entry_t *e = vlib_stats_get_entry (sm, entry_index);
+ va_list va;
+ void *oldheap;
+
+ oldheap = clib_mem_set_heap (sm->heap);
+ vlib_stats_segment_lock ();
+
+ vec_validate (e->string_vector, vector_index);
+ vec_reset_length (e->string_vector[vector_index]);
+
+ va_start (va, fmt);
+ e->string_vector[vector_index] =
+ va_format (e->string_vector[vector_index], fmt, &va);
+ va_end (va);
+
+ vlib_stats_segment_unlock ();
+ clib_mem_set_heap (oldheap);
+}
+
+u32
+vlib_stats_add_counter_vector (char *fmt, ...)
+{
+ va_list va;
+ u8 *name;
+
+ va_start (va, fmt);
+ name = va_format (0, fmt, &va);
+ va_end (va);
+ return vlib_stats_new_entry_internal (STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE,
+ name);
+}
+
+void
+vlib_stats_validate_counter_vector (u32 entry_index, u32 vector_index)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_entry_t *e = vlib_stats_get_entry (sm, entry_index);
+ void *oldheap;
+ counter_t **c = e->data;
+
+ if (vec_len (c) > 0 && vec_len (c[0]) > vector_index)
+ return;
+
+ oldheap = clib_mem_set_heap (sm->heap);
+ vlib_stats_segment_lock ();
+
+ vec_validate_aligned (c, 0, CLIB_CACHE_LINE_BYTES);
+ vec_validate_aligned (c[0], vector_index, CLIB_CACHE_LINE_BYTES);
+ e->data = c;
+
+ vlib_stats_segment_unlock ();
+ clib_mem_set_heap (oldheap);
+}
+
+u32
+vlib_stats_add_symlink (u32 entry_index, u32 vector_index, char *fmt, ...)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_shared_header_t *shared_header = sm->shared_header;
+ vlib_stats_entry_t e;
+ va_list va;
+ u8 *name;
+
+ ASSERT (shared_header);
+ ASSERT (entry_index < vec_len (sm->directory_vector));
+
+ va_start (va, fmt);
+ name = va_format (0, fmt, &va);
+ va_end (va);
+
+ if (vlib_stats_find_entry_index ("%v", name) == STAT_SEGMENT_INDEX_INVALID)
+ {
+ vec_add1 (name, 0);
+ vlib_stats_set_entry_name (&e, (char *) name);
+ e.type = STAT_DIR_TYPE_SYMLINK;
+ e.index1 = entry_index;
+ e.index2 = vector_index;
+ vector_index = vlib_stats_create_counter (&e);
+
+ /* Warn clients to refresh any pointers they might be holding */
+ shared_header->directory_vector = sm->directory_vector;
+ }
+ else
+ vector_index = ~0;
+
+ vec_free (name);
+ return vector_index;
+}
+
+void
+vlib_stats_rename_symlink (u64 entry_index, char *fmt, ...)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_entry_t *e = vlib_stats_get_entry (sm, entry_index);
+ va_list va;
+ u8 *new_name;
+
+ hash_unset_str_key_free (&sm->directory_vector_by_name, e->name);
+
+ va_start (va, fmt);
+ new_name = va_format (0, fmt, &va);
+ va_end (va);
+
+ vec_add1 (new_name, 0);
+ vlib_stats_set_entry_name (e, (char *) new_name);
+ hash_set_str_key_alloc (&sm->directory_vector_by_name, e->name, entry_index);
+ vec_free (new_name);
+}
+
+f64
+vlib_stats_get_segment_update_rate (void)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ return sm->update_interval;
+}
+
+void
+vlib_stats_register_collector_fn (vlib_stats_collector_reg_t *reg)
+{
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ vlib_stats_collector_t *c;
+
+ ASSERT (reg->entry_index != ~0);
+
+ pool_get_zero (sm->collectors, c);
+ c->fn = reg->collect_fn;
+ c->entry_index = reg->entry_index;
+ c->vector_index = reg->vector_index;
+ c->private_data = reg->private_data;
+
+ return;
+}
diff --git a/src/vlib/stats/stats.h b/src/vlib/stats/stats.h
new file mode 100644
index 00000000000..25d7c4ba484
--- /dev/null
+++ b/src/vlib/stats/stats.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2022 Cisco Systems, Inc.
+ */
+
+#ifndef included_stats_stats_h
+#define included_stats_stats_h
+
+#include <vppinfra/socket.h>
+#include <vppinfra/lock.h>
+#include <vlib/stats/shared.h>
+
+/* Default stat segment 32m */
+#define STAT_SEGMENT_DEFAULT_SIZE (32 << 20)
+
+/* Shared segment memory layout version */
+#define STAT_SEGMENT_VERSION 2
+
+#define STAT_SEGMENT_INDEX_INVALID UINT32_MAX
+
+typedef enum
+{
+ STAT_COUNTER_HEARTBEAT = 0,
+ STAT_COUNTER_LAST_STATS_CLEAR,
+ STAT_COUNTER_NODE_CLOCKS,
+ STAT_COUNTER_NODE_VECTORS,
+ STAT_COUNTER_NODE_CALLS,
+ STAT_COUNTER_NODE_SUSPENDS,
+ STAT_COUNTER_NODE_NAMES,
+ STAT_COUNTERS
+} stat_segment_counter_t;
+
+#define foreach_stat_segment_node_counter_name \
+ _ (NODE_CLOCKS, COUNTER_VECTOR_SIMPLE, clocks, "/sys/node") \
+ _ (NODE_VECTORS, COUNTER_VECTOR_SIMPLE, vectors, "/sys/node") \
+ _ (NODE_CALLS, COUNTER_VECTOR_SIMPLE, calls, "/sys/node") \
+ _ (NODE_SUSPENDS, COUNTER_VECTOR_SIMPLE, suspends, "/sys/node")
+
+#define foreach_stat_segment_counter_name \
+ _ (LAST_STATS_CLEAR, SCALAR_INDEX, last_stats_clear, "/sys") \
+ _ (HEARTBEAT, SCALAR_INDEX, heartbeat, "/sys") \
+ _ (NODE_NAMES, NAME_VECTOR, names, "/sys/node") \
+ foreach_stat_segment_node_counter_name
+
+typedef struct
+{
+ u32 entry_index;
+ u32 vector_index;
+ u64 private_data;
+ vlib_stats_entry_t *entry;
+} vlib_stats_collector_data_t;
+
+typedef void (*vlib_stats_collector_fn_t) (vlib_stats_collector_data_t *);
+
+typedef struct
+{
+ vlib_stats_collector_fn_t collect_fn;
+ u32 entry_index;
+ u32 vector_index;
+ u64 private_data;
+} vlib_stats_collector_reg_t;
+
+typedef struct
+{
+ vlib_stats_collector_fn_t fn;
+ u32 entry_index;
+ u32 vector_index;
+ u64 private_data;
+} vlib_stats_collector_t;
+
+typedef struct
+{
+ /* internal, does not point to shared memory */
+ vlib_stats_collector_t *collectors;
+
+ /* statistics segment */
+ uword *directory_vector_by_name;
+ vlib_stats_entry_t *directory_vector;
+ volatile u64 **error_vector;
+ u8 **nodes;
+
+ /* Update interval */
+ f64 update_interval;
+
+ clib_spinlock_t *stat_segment_lockp;
+ clib_socket_t *socket;
+ u8 *socket_name;
+ ssize_t memory_size;
+ clib_mem_page_sz_t log2_page_sz;
+ u8 node_counters_enabled;
+ void *heap;
+ vlib_stats_shared_header_t
+ *shared_header; /* pointer to shared memory segment */
+ int memfd;
+
+} vlib_stats_segment_t;
+
+typedef struct
+{
+ vlib_stats_segment_t segment;
+} vlib_stats_main_t;
+
+extern vlib_stats_main_t vlib_stats_main;
+
+static_always_inline vlib_stats_segment_t *
+vlib_stats_get_segment ()
+{
+ return &vlib_stats_main.segment;
+}
+
+static_always_inline vlib_stats_entry_t *
+vlib_stats_get_entry (vlib_stats_segment_t *sm, u32 entry_index)
+{
+ vlib_stats_entry_t *e;
+ ASSERT (entry_index < vec_len (sm->directory_vector));
+ e = sm->directory_vector + entry_index;
+ ASSERT (e->type != STAT_DIR_TYPE_EMPTY && e->type != STAT_DIR_TYPE_ILLEGAL);
+ return e;
+}
+
+clib_error_t *vlib_stats_init (vlib_main_t *vm);
+void *vlib_stats_set_heap ();
+void vlib_stats_update_counter (void *, u32, stat_directory_type_t);
+void vlib_stats_register_error_index (u64 *em_vec, u64 index, char *fmt, ...);
+void vlib_stats_update_error_vector (u64 *error_vector, u32 thread_index,
+ int lock);
+void vlib_stats_segment_lock (void);
+void vlib_stats_segment_unlock (void);
+void vlib_stats_delete_cm (void *);
+void vlib_stats_register_mem_heap (clib_mem_heap_t *);
+f64 vlib_stats_get_segment_update_rate (void);
+
+/* gauge */
+u32 vlib_stats_add_gauge (char *fmt, ...);
+void vlib_stats_set_gauge (u32 entry_index, u64 value);
+
+/* timestamp */
+u32 vlib_stats_add_timestamp (char *fmt, ...);
+void vlib_stats_set_timestamp (u32 entry_index, f64 value);
+
+/* vector */
+u32 vlib_stats_add_counter_vector (char *fmt, ...);
+void vlib_stats_validate_counter_vector (u32 entry_index, u32 vector_index);
+
+/* string vector */
+u32 vlib_stats_add_string_vector (char *fmt, ...);
+void vlib_stats_set_string_vector (u32 entry_index, u32 vector_index,
+ char *fmt, ...);
+
+/* symlink */
+u32 vlib_stats_add_symlink (u32 entry_index, u32 vector_index, char *fmt, ...);
+void vlib_stats_rename_symlink (u64 entry_index, char *fmt, ...);
+
+/* common to all types */
+void vlib_stats_remove_entry (u32 entry_index);
+u32 vlib_stats_find_entry_index (char *fmt, ...);
+void vlib_stats_register_collector_fn (vlib_stats_collector_reg_t *r);
+
+format_function_t format_vlib_stats_symlink;
+
+#endif