diff options
Diffstat (limited to 'src/vlib/stats')
-rw-r--r-- | src/vlib/stats/cli.c | 121 | ||||
-rw-r--r-- | src/vlib/stats/collector.c | 186 | ||||
-rw-r--r-- | src/vlib/stats/format.c | 21 | ||||
-rw-r--r-- | src/vlib/stats/init.c | 258 | ||||
-rw-r--r-- | src/vlib/stats/provider_mem.c | 68 | ||||
-rw-r--r-- | src/vlib/stats/shared.h | 50 | ||||
-rw-r--r-- | src/vlib/stats/stats.c | 574 | ||||
-rw-r--r-- | src/vlib/stats/stats.h | 164 |
8 files changed, 1442 insertions, 0 deletions
diff --git a/src/vlib/stats/cli.c b/src/vlib/stats/cli.c new file mode 100644 index 00000000000..94a852ac751 --- /dev/null +++ b/src/vlib/stats/cli.c @@ -0,0 +1,121 @@ +/* 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_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..b23f3df5713 --- /dev/null +++ b/src/vlib/stats/collector.c @@ -0,0 +1,186 @@ +/* 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> + +enum +{ + NODE_CLOCKS, + NODE_VECTORS, + NODE_CALLS, + NODE_SUSPENDS, + N_NODE_COUNTERS +}; + +struct +{ + u32 entry_index; + char *name; +} node_counters[] = { + [NODE_CLOCKS] = { .name = "clocks" }, + [NODE_VECTORS] = { .name = "vectors" }, + [NODE_CALLS] = { .name = "calls" }, + [NODE_SUSPENDS] = { .name = "suspends" }, +}; + +static struct +{ + u8 *name; + u32 symlinks[N_NODE_COUNTERS]; +} *node_data = 0; + +static vlib_stats_string_vector_t node_names = 0; + +static inline void +update_node_counters (vlib_stats_segment_t *sm) +{ + clib_bitmap_t *bmp = 0; + vlib_main_t **stat_vms = 0; + vlib_node_t ***node_dups = 0; + u32 n_nodes; + int i, j; + + vlib_node_get_nodes (0 /* vm, for barrier sync */, + (u32) ~0 /* all threads */, 1 /* include stats */, + 0 /* barrier sync */, &node_dups, &stat_vms); + + n_nodes = vec_len (node_dups[0]); + + vec_validate (node_data, n_nodes - 1); + + for (i = 0; i < n_nodes; i++) + if (vec_is_equal (node_data[i].name, node_dups[0][i]->name) == 0) + bmp = clib_bitmap_set (bmp, i, 1); + + if (bmp) + { + u32 last_thread = vlib_get_n_threads (); + vlib_stats_segment_lock (); + clib_bitmap_foreach (i, bmp) + { + if (node_data[i].name) + { + vec_free (node_data[i].name); + for (j = 0; j < ARRAY_LEN (node_data->symlinks); j++) + vlib_stats_remove_entry (node_data[i].symlinks[j]); + } + } + /* We can't merge the loops because a node index corresponding to a given + * node name can change between 2 updates. Otherwise, we could add + * already existing symlinks or delete valid ones. + */ + clib_bitmap_foreach (i, bmp) + { + vlib_node_t *n = node_dups[0][i]; + node_data[i].name = vec_dup (n->name); + vlib_stats_set_string_vector (&node_names, n->index, "%v", n->name); + + for (int j = 0; j < ARRAY_LEN (node_counters); j++) + { + vlib_stats_validate (node_counters[j].entry_index, last_thread, + n_nodes - 1); + node_data[i].symlinks[j] = vlib_stats_add_symlink ( + node_counters[j].entry_index, n->index, "/nodes/%U/%s", + format_vlib_stats_symlink, n->name, node_counters[j].name); + ASSERT (node_data[i].symlinks[j] != CLIB_U32_MAX); + } + } + vlib_stats_segment_unlock (); + vec_free (bmp); + } + + 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]; + + counters = vlib_stats_get_entry_data_pointer ( + node_counters[NODE_CLOCKS].entry_index); + c = counters[j]; + c[n->index] = n->stats_total.clocks - n->stats_last_clear.clocks; + + counters = vlib_stats_get_entry_data_pointer ( + node_counters[NODE_VECTORS].entry_index); + c = counters[j]; + c[n->index] = n->stats_total.vectors - n->stats_last_clear.vectors; + + counters = vlib_stats_get_entry_data_pointer ( + node_counters[NODE_CALLS].entry_index); + c = counters[j]; + c[n->index] = n->stats_total.calls - n->stats_last_clear.calls; + + counters = vlib_stats_get_entry_data_pointer ( + node_counters[NODE_SUSPENDS].entry_index); + 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 (); + + if (sm->node_counters_enabled) + { + node_names = vlib_stats_add_string_vector ("/sys/node/names"); + ASSERT (node_names); + + for (int x = 0; x < ARRAY_LEN (node_counters); x++) + { + node_counters[x].entry_index = vlib_stats_add_counter_vector ( + "/sys/node/%s", node_counters[x].name); + ASSERT (node_counters[x].entry_index != CLIB_U32_MAX); + } + } + + sm->directory_vector[STAT_COUNTER_BOOTTIME].value = unix_time_now (); + + 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..54c11aff743 --- /dev/null +++ b/src/vlib/stats/format.c @@ -0,0 +1,21 @@ +/* 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) +{ + u8 *input = va_arg (*args, u8 *); + + for (int i = 0; i < vec_len (input); i++) + if (input[i] == '/') + vec_add1 (s, '_'); + else + vec_add1 (s, input[i]); + + return s; +} diff --git a/src/vlib/stats/init.c b/src/vlib/stats/init.c new file mode 100644 index 00000000000..8b382daf333 --- /dev/null +++ b/src/vlib/stats/init.c @@ -0,0 +1,258 @@ +/* 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 u32 vlib_loops_stats_counter_index; + +static void +vector_rate_collector_fn (vlib_stats_collector_data_t *d) +{ + vlib_main_t *this_vlib_main; + counter_t **counters, **loops_counters; + counter_t *cb, *loops_cb; + f64 vector_rate = 0.0; + u32 i, n_threads = vlib_get_n_threads (); + + vlib_stats_validate (d->entry_index, 0, n_threads - 1); + counters = d->entry->data; + cb = counters[0]; + + vlib_stats_validate (vlib_loops_stats_counter_index, 0, n_threads - 1); + loops_counters = + vlib_stats_get_entry_data_pointer (vlib_loops_stats_counter_index); + loops_cb = loops_counters[0]; + + 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; + + loops_cb[i] = this_vlib_main->loops_per_second; + } + + /* 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 = {}; + + 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)); + sm->locking_thread_index = ~0; + sm->n_locks = 0; + clib_spinlock_init (sm->stat_segment_lockp); + + /* Set up the name to counter-vector hash table */ + sm->directory_vector = + vec_new_heap (typeof (sm->directory_vector[0]), STAT_COUNTERS, heap); + sm->dir_vector_first_free_elt = CLIB_U32_MAX; + + shared_header->epoch = 1; + + /* Scalar stats and node counters */ +#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; + + 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_loops_stats_counter_index = + vlib_stats_add_counter_vector ("/sys/loops_per_worker"); + vlib_stats_register_collector_fn (®); + vlib_stats_validate (reg.entry_index, 0, vlib_get_n_threads ()); + vlib_stats_validate (vlib_loops_stats_counter_index, 0, + 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..f3a3f5d3ed4 --- /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 (idx, 0, STAT_MEM_RELEASABLE); + + /* Create symlink */ + vlib_stats_add_symlink (idx, STAT_MEM_USED, "/mem/%s/used", heap->name); + vlib_stats_add_symlink (idx, STAT_MEM_TOTAL, "/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..8e44ce3dc86 --- /dev/null +++ b/src/vlib/stats/shared.h @@ -0,0 +1,50 @@ +/* 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_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; +} 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..b7743ec70f2 --- /dev/null +++ b/src/vlib/stats/stats.c @@ -0,0 +1,574 @@ +/* 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; + +void +vlib_stats_segment_lock (void) +{ + vlib_main_t *vm = vlib_get_main (); + vlib_stats_segment_t *sm = vlib_stats_get_segment (); + + /* already locked by us */ + if (sm->shared_header->in_progress && + vm->thread_index == sm->locking_thread_index) + goto done; + + ASSERT (sm->locking_thread_index == ~0); + ASSERT (sm->shared_header->in_progress == 0); + ASSERT (sm->n_locks == 0); + + clib_spinlock_lock (sm->stat_segment_lockp); + + sm->shared_header->in_progress = 1; + sm->locking_thread_index = vm->thread_index; +done: + sm->n_locks++; +} + +void +vlib_stats_segment_unlock (void) +{ + vlib_main_t *vm = vlib_get_main (); + vlib_stats_segment_t *sm = vlib_stats_get_segment (); + + ASSERT (sm->shared_header->in_progress == 1); + ASSERT (sm->locking_thread_index == vm->thread_index); + ASSERT (sm->n_locks > 0); + + sm->n_locks--; + + if (sm->n_locks > 0) + return; + + sm->shared_header->epoch++; + __atomic_store_n (&sm->shared_header->in_progress, 0, __ATOMIC_RELEASE); + sm->locking_thread_index = ~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 (); + u32 index; + + if (sm->dir_vector_first_free_elt != CLIB_U32_MAX) + { + index = sm->dir_vector_first_free_elt; + sm->dir_vector_first_free_elt = sm->directory_vector[index].index; + } + else + { + index = vec_len (sm->directory_vector); + vec_validate (sm->directory_vector, index); + } + + sm->directory_vector[index] = *e; + + 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); + counter_t **c; + vlib_counter_t **vc; + void *oldheap; + u32 i; + + if (entry_index >= vec_len (sm->directory_vector)) + return; + + vlib_stats_segment_lock (); + + 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; + oldheap = clib_mem_set_heap (sm->heap); + for (i = 0; i < vec_len (c); i++) + vec_free (c[i]); + vec_free (c); + clib_mem_set_heap (oldheap); + break; + + case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED: + vc = e->data; + e->data = 0; + oldheap = clib_mem_set_heap (sm->heap); + for (i = 0; i < vec_len (vc); i++) + vec_free (vc[i]); + vec_free (vc); + clib_mem_set_heap (oldheap); + break; + + case STAT_DIR_TYPE_SCALAR_INDEX: + case STAT_DIR_TYPE_SYMLINK: + break; + default: + ASSERT (0); + } + + vlib_stats_segment_unlock (); + + hash_unset_str_key_free (&sm->directory_vector_by_name, e->name); + + memset (e, 0, sizeof (*e)); + e->type = STAT_DIR_TYPE_EMPTY; + + e->value = sm->dir_vector_first_free_elt; + sm->dir_vector_first_free_elt = entry_index; +} + +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; +} + +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; +} + +vlib_stats_string_vector_t +vlib_stats_add_string_vector (char *fmt, ...) +{ + vlib_stats_segment_t *sm = vlib_stats_get_segment (); + va_list va; + vlib_stats_header_t *sh; + vlib_stats_string_vector_t sv; + u32 index; + u8 *name; + + va_start (va, fmt); + name = va_format (0, fmt, &va); + va_end (va); + + index = vlib_stats_new_entry_internal (STAT_DIR_TYPE_NAME_VECTOR, name); + if (index == CLIB_U32_MAX) + return 0; + + sv = vec_new_generic (vlib_stats_string_vector_t, 0, + sizeof (vlib_stats_header_t), 0, sm->heap); + sh = vec_header (sv); + sh->entry_index = index; + sm->directory_vector[index].string_vector = sv; + return sv; +} + +void +vlib_stats_set_string_vector (vlib_stats_string_vector_t *svp, + u32 vector_index, char *fmt, ...) +{ + vlib_stats_segment_t *sm = vlib_stats_get_segment (); + vlib_stats_header_t *sh = vec_header (*svp); + vlib_stats_entry_t *e = vlib_stats_get_entry (sm, sh->entry_index); + va_list va; + u8 *s; + + if (fmt[0] == 0) + { + if (vec_len (e->string_vector) <= vector_index) + return; + + if (e->string_vector[vector_index] == 0) + return; + + vlib_stats_segment_lock (); + vec_free (e->string_vector[vector_index]); + vlib_stats_segment_unlock (); + return; + } + + vlib_stats_segment_lock (); + + ASSERT (e->string_vector); + + vec_validate (e->string_vector, vector_index); + svp[0] = e->string_vector; + + s = e->string_vector[vector_index]; + + if (s == 0) + s = vec_new_heap (u8 *, 0, sm->heap); + + vec_reset_length (s); + + va_start (va, fmt); + s = va_format (s, fmt, &va); + va_end (va); + vec_add1 (s, 0); + + e->string_vector[vector_index] = s; + + vlib_stats_segment_unlock (); +} + +void +vlib_stats_free_string_vector (vlib_stats_string_vector_t *sv) +{ + vlib_stats_header_t *sh = vec_header (*sv); + vlib_stats_remove_entry (sh->entry_index); +} + +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); +} + +u32 +vlib_stats_add_counter_pair_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_COMBINED, + name); +} + +static int +vlib_stats_validate_will_expand_internal (u32 entry_index, va_list *va) +{ + vlib_stats_segment_t *sm = vlib_stats_get_segment (); + vlib_stats_entry_t *e = vlib_stats_get_entry (sm, entry_index); + void *oldheap; + int rv = 1; + + oldheap = clib_mem_set_heap (sm->heap); + if (e->type == STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE) + { + u32 idx0 = va_arg (*va, u32); + u32 idx1 = va_arg (*va, u32); + u64 **data = e->data; + + if (idx0 >= vec_len (data)) + goto done; + + for (u32 i = 0; i <= idx0; i++) + if (idx1 >= vec_max_len (data[i])) + goto done; + } + else if (e->type == STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED) + { + u32 idx0 = va_arg (*va, u32); + u32 idx1 = va_arg (*va, u32); + vlib_counter_t **data = e->data; + + va_end (*va); + + if (idx0 >= vec_len (data)) + goto done; + + for (u32 i = 0; i <= idx0; i++) + if (idx1 >= vec_max_len (data[i])) + goto done; + } + else + ASSERT (0); + + rv = 0; +done: + clib_mem_set_heap (oldheap); + return rv; +} + +int +vlib_stats_validate_will_expand (u32 entry_index, ...) +{ + va_list va; + int rv; + + va_start (va, entry_index); + rv = vlib_stats_validate_will_expand_internal (entry_index, &va); + va_end (va); + return rv; +} + +void +vlib_stats_validate (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; + va_list va; + int will_expand; + + va_start (va, entry_index); + will_expand = vlib_stats_validate_will_expand_internal (entry_index, &va); + va_end (va); + + if (will_expand) + vlib_stats_segment_lock (); + + oldheap = clib_mem_set_heap (sm->heap); + + va_start (va, entry_index); + + if (e->type == STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE) + { + u32 idx0 = va_arg (va, u32); + u32 idx1 = va_arg (va, u32); + u64 **data = e->data; + + vec_validate_aligned (data, idx0, CLIB_CACHE_LINE_BYTES); + + for (u32 i = 0; i <= idx0; i++) + vec_validate_aligned (data[i], idx1, CLIB_CACHE_LINE_BYTES); + e->data = data; + } + else if (e->type == STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED) + { + u32 idx0 = va_arg (va, u32); + u32 idx1 = va_arg (va, u32); + vlib_counter_t **data = e->data; + + vec_validate_aligned (data, idx0, CLIB_CACHE_LINE_BYTES); + + for (u32 i = 0; i <= idx0; i++) + vec_validate_aligned (data[i], idx1, CLIB_CACHE_LINE_BYTES); + e->data = data; + } + else + ASSERT (0); + + va_end (va); + + clib_mem_set_heap (oldheap); + + if (will_expand) + vlib_stats_segment_unlock (); +} + +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..ab1e2828c5a --- /dev/null +++ b/src/vlib/stats/stats.h @@ -0,0 +1,164 @@ +/* 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_BOOTTIME, + STAT_COUNTERS +} stat_segment_counter_t; + +#define foreach_stat_segment_counter_name \ + _ (LAST_STATS_CLEAR, SCALAR_INDEX, last_stats_clear, "/sys") \ + _ (HEARTBEAT, SCALAR_INDEX, heartbeat, "/sys") \ + _ (BOOTTIME, SCALAR_INDEX, boottime, "/sys") + +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; + u32 dir_vector_first_free_elt; + + /* Update interval */ + f64 update_interval; + + clib_spinlock_t *stat_segment_lockp; + u32 locking_thread_index; + u32 n_locks; + 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 +{ + u32 entry_index; +} vlib_stats_header_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; +} + +static_always_inline void * +vlib_stats_get_entry_data_pointer (u32 entry_index) +{ + vlib_stats_segment_t *sm = vlib_stats_get_segment (); + vlib_stats_entry_t *e = vlib_stats_get_entry (sm, entry_index); + return e->data; +} + +clib_error_t *vlib_stats_init (vlib_main_t *vm); +void *vlib_stats_set_heap (); +void vlib_stats_segment_lock (void); +void vlib_stats_segment_unlock (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); + +/* counter vector */ +u32 vlib_stats_add_counter_vector (char *fmt, ...); + +/* counter pair vector */ +u32 vlib_stats_add_counter_pair_vector (char *fmt, ...); + +/* string vector */ +typedef u8 **vlib_stats_string_vector_t; +vlib_stats_string_vector_t vlib_stats_add_string_vector (char *fmt, ...); +void vlib_stats_set_string_vector (vlib_stats_string_vector_t *sv, u32 index, + char *fmt, ...); +void vlib_stats_free_string_vector (vlib_stats_string_vector_t *sv); + +/* 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_validate (u32 entry_index, ...); +int vlib_stats_validate_will_expand (u32 entry_index, ...); +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 |