diff options
author | 2018-08-23 13:00:53 +0200 | |
---|---|---|
committer | 2018-08-29 09:39:38 +0000 | |
commit | 2fee16787ed0d622631223567635a77e14c8c076 (patch) | |
tree | aab9492cc925b7cfa769d996fd6fbbf1d0506971 /src/vpp | |
parent | fcf9497d3bcd34b8b5090ee053575296cf56c5e6 (diff) |
STATS: stat_client updates.
New stat segment client library: vpp-api/client/stat_client.h
New stat segment query app: vpp_get_stats [ls | dump | poll ] <pattern ...>
Prometheus integration through: vpp_prometheus_export <pattern>
Change-Id: I6f370cf599e9fcf066f22965a62d3a8acd529994
Signed-off-by: Ole Troan <ot@cisco.com>
Diffstat (limited to 'src/vpp')
-rw-r--r-- | src/vpp/CMakeLists.txt | 11 | ||||
-rw-r--r-- | src/vpp/app/stat_client.c | 378 | ||||
-rw-r--r-- | src/vpp/app/stat_client.h | 66 | ||||
-rw-r--r-- | src/vpp/app/vpp_get_stats.c | 242 | ||||
-rw-r--r-- | src/vpp/app/vpp_prometheus_export.c | 320 | ||||
-rw-r--r-- | src/vpp/stats/stat_segment.c | 18 | ||||
-rw-r--r-- | src/vpp/stats/stats.h | 4 |
7 files changed, 588 insertions, 451 deletions
diff --git a/src/vpp/CMakeLists.txt b/src/vpp/CMakeLists.txt index 7e59090fc2e..78ca867abff 100644 --- a/src/vpp/CMakeLists.txt +++ b/src/vpp/CMakeLists.txt @@ -110,10 +110,15 @@ add_vpp_executable(summary_stats_client NO_INSTALL ) -add_vpp_executable(stat_client - SOURCES app/stat_client.c - LINK_LIBRARIES vppinfra svm vlibmemoryclient +add_vpp_executable(vpp_get_stats + SOURCES app/vpp_get_stats.c + LINK_LIBRARIES vppapiclient vppinfra svm vlibmemoryclient DEPENDS api_headers NO_INSTALL ) +add_vpp_executable(vpp_prometheus_export + SOURCES app/vpp_prometheus_export.c + LINK_LIBRARIES vppapiclient vppinfra svm vlibmemoryclient + DEPENDS api_headers +) diff --git a/src/vpp/app/stat_client.c b/src/vpp/app/stat_client.c deleted file mode 100644 index 2bedf6f2d7d..00000000000 --- a/src/vpp/app/stat_client.c +++ /dev/null @@ -1,378 +0,0 @@ -/* - *------------------------------------------------------------------ - * stat_client.c - * - * Copyright (c) 2018 Cisco and/or its affiliates. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *------------------------------------------------------------------ - */ - -#include <vpp/app/stat_client.h> - -stat_client_main_t stat_client_main; - -static int -stat_segment_connect (stat_client_main_t * sm) -{ - ssvm_private_t *ssvmp = &sm->stat_segment; - ssvm_shared_header_t *shared_header; - clib_socket_t s = { 0 }; - clib_error_t *err; - int fd = -1, retval; - - s.config = (char *) sm->socket_name; - s.flags = CLIB_SOCKET_F_IS_CLIENT | CLIB_SOCKET_F_SEQPACKET; - err = clib_socket_init (&s); - if (err) - { - clib_error_report (err); - exit (1); - } - err = clib_socket_recvmsg (&s, 0, 0, &fd, 1); - if (err) - { - clib_error_report (err); - return -1; - } - clib_socket_close (&s); - - memset (ssvmp, 0, sizeof (*ssvmp)); - ssvmp->fd = fd; - - /* Note: this closes memfd.fd */ - retval = ssvm_slave_init_memfd (ssvmp); - if (retval) - { - clib_warning ("WARNING: segment map returned %d", retval); - return -1; - } - - fformat (stdout, "Stat segment mapped OK...\n"); - - ASSERT (ssvmp && ssvmp->sh); - - /* Pick up the segment lock from the shared memory header */ - shared_header = ssvmp->sh; - sm->stat_segment_lockp = (clib_spinlock_t *) (shared_header->opaque[0]); - sm->segment_ready = 1; - - return 0; -} - -#define foreach_cached_pointer \ -_(/sys/vector_rate, SCALAR_POINTER, &stat_client_main.vector_rate_ptr) \ -_(/sys/input_rate, SCALAR_POINTER, &stat_client_main.input_rate_ptr) \ -_(/sys/last_update, SCALAR_POINTER, &stat_client_main.last_runtime_ptr) \ -_(/sys/last_stats_clear, SCALAR_POINTER, \ - &stat_client_main.last_runtime_stats_clear_ptr) \ -_(/if/rx, COUNTER_VECTOR, &stat_client_main.intfc_rx_counters) \ -_(/if/tx, COUNTER_VECTOR, &stat_client_main.intfc_tx_counters) \ -_(/err/0/counter_vector, VECTOR_POINTER, \ - &stat_client_main.thread_0_error_counts) \ -_(serialized_nodes, SERIALIZED_NODES, \ - &stat_client_main.serialized_nodes) - -typedef struct -{ - char *name; - stat_directory_type_t type; - void *valuep; -} cached_pointer_t; - -cached_pointer_t cached_pointers[] = { -#define _(n,t,p) {#n, STAT_DIR_TYPE_##t, (void *)p}, - foreach_cached_pointer -#undef _ -}; - -static void -maybe_update_cached_pointers (stat_client_main_t * sm, - ssvm_shared_header_t * shared_header) -{ - uword *p, *counter_vector_by_name; - int i; - stat_segment_directory_entry_t *ep; - cached_pointer_t *cp; - u64 *valuep; - - /* Cached pointers OK? */ - if (sm->current_epoch == - (u64) shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH]) - return; - - fformat (stdout, "Updating cached pointers...\n"); - - /* Nope, fix them... */ - counter_vector_by_name = (uword *) - shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR]; - - for (i = 0; i < ARRAY_LEN (cached_pointers); i++) - { - cp = &cached_pointers[i]; - - p = hash_get_mem (counter_vector_by_name, cp->name); - - if (p == 0) - { - clib_warning ("WARN: %s not in directory!", cp->name); - continue; - } - ep = (stat_segment_directory_entry_t *) (p[0]); - ASSERT (ep->type == cp->type); - valuep = (u64 *) cp->valuep; - *valuep = (u64) ep->value; - } - - /* And remember that we did... */ - sm->current_epoch = (u64) shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH]; -} - -static void -stat_poll_loop (stat_client_main_t * sm) -{ - struct timespec ts, tsrem; - ssvm_private_t *ssvmp = &sm->stat_segment; - ssvm_shared_header_t *shared_header; - vlib_counter_t *thread0_rx_counters = 0, *thread0_tx_counters = 0; - vlib_node_t ***nodes_by_thread; - vlib_node_t **nodes; - vlib_node_t *n; - f64 vector_rate, input_rate; - u32 len; - int i, j; - u32 source_address_match_errors; - - /* Wait until the stats segment is mapped */ - while (!sm->segment_ready) - { - ts.tv_sec = 0; - ts.tv_nsec = 100000000; - while (nanosleep (&ts, &tsrem) < 0) - ts = tsrem; - } - - shared_header = ssvmp->sh; - ASSERT (ssvmp->sh); - - while (1) - { - /* Scrape stats every 5 seconds */ - ts.tv_sec = 5; - ts.tv_nsec = 0; - while (nanosleep (&ts, &tsrem) < 0) - ts = tsrem; - - vec_reset_length (thread0_rx_counters); - vec_reset_length (thread0_tx_counters); - - /* Grab the stats segment lock */ - clib_spinlock_lock (sm->stat_segment_lockp); - - /* see if we need to update cached pointers */ - maybe_update_cached_pointers (sm, shared_header); - - ASSERT (sm->vector_rate_ptr); - ASSERT (sm->intfc_rx_counters); - ASSERT (sm->intfc_tx_counters); - - /* Read data from the segment */ - vector_rate = *sm->vector_rate_ptr; - input_rate = *sm->input_rate_ptr; - - len = vec_len (sm->intfc_rx_counters[0]); - - ASSERT (len); - - vec_validate (thread0_rx_counters, len - 1); - vec_validate (thread0_tx_counters, len - 1); - - clib_memcpy (thread0_rx_counters, sm->intfc_rx_counters[0], - len * sizeof (vlib_counter_t)); - clib_memcpy (thread0_tx_counters, sm->intfc_tx_counters[0], - len * sizeof (vlib_counter_t)); - - source_address_match_errors = - sm->thread_0_error_counts[sm->source_address_match_error_index]; - - /* Drop the lock */ - clib_spinlock_unlock (sm->stat_segment_lockp); - - /* And print results... */ - - fformat (stdout, "vector_rate %.2f input_rate %.2f\n", - vector_rate, input_rate); - - for (i = 0; i < vec_len (thread0_rx_counters); i++) - { - fformat (stdout, "[%d]: %lld rx packets, %lld rx bytes\n", - i, thread0_rx_counters[i].packets, - thread0_rx_counters[i].bytes); - fformat (stdout, "[%d]: %lld tx packets, %lld tx bytes\n", - i, thread0_tx_counters[i].packets, - thread0_tx_counters[i].bytes); - } - - fformat (stdout, "%lld source address match errors\n", - source_address_match_errors); - - if (sm->serialized_nodes) - { - nodes_by_thread = vlib_node_unserialize (sm->serialized_nodes); - - /* Across all threads... */ - for (i = 0; i < vec_len (nodes_by_thread); i++) - { - u64 n_input, n_output, n_drop, n_punt; - u64 n_internal_vectors, n_internal_calls; - u64 n_clocks, l, v, c; - f64 dt; - - nodes = nodes_by_thread[i]; - - fformat (stdout, "Thread %d -------------------------\n", i); - - n_input = n_output = n_drop = n_punt = n_clocks = 0; - n_internal_vectors = n_internal_calls = 0; - - /* Across all nodes */ - for (j = 0; j < vec_len (nodes); j++) - { - n = nodes[j]; - - /* Exactly stolen from node_cli.c... */ - l = n->stats_total.clocks - n->stats_last_clear.clocks; - n_clocks += l; - - v = n->stats_total.vectors - n->stats_last_clear.vectors; - c = n->stats_total.calls - n->stats_last_clear.calls; - - switch (n->type) - { - default: - vec_free (n->name); - vec_free (n->next_nodes); - vec_free (n); - continue; - - case VLIB_NODE_TYPE_INTERNAL: - n_output += - (n->flags & VLIB_NODE_FLAG_IS_OUTPUT) ? v : 0; - n_drop += (n->flags & VLIB_NODE_FLAG_IS_DROP) ? v : 0; - n_punt += (n->flags & VLIB_NODE_FLAG_IS_PUNT) ? v : 0; - if (!(n->flags & VLIB_NODE_FLAG_IS_OUTPUT)) - { - n_internal_vectors += v; - n_internal_calls += c; - } - if (n->flags & VLIB_NODE_FLAG_IS_HANDOFF) - n_input += v; - break; - - case VLIB_NODE_TYPE_INPUT: - n_input += v; - break; - } - - if (n->stats_total.calls) - { - fformat (stdout, - "%s (%s): clocks %lld calls %lld vectors %lld ", - n->name, - n->state_string, - n->stats_total.clocks, - n->stats_total.calls, n->stats_total.vectors); - if (n->stats_total.vectors) - fformat (stdout, "clocks/pkt %.2f\n", - (f64) n->stats_total.clocks / - (f64) n->stats_total.vectors); - else - fformat (stdout, "\n"); - } - vec_free (n->name); - vec_free (n->next_nodes); - vec_free (n); - } - - fformat (stdout, "average vectors/node %.2f\n", - (n_internal_calls > 0 - ? (f64) n_internal_vectors / (f64) n_internal_calls - : 0)); - - - dt = *sm->last_runtime_ptr - *sm->last_runtime_stats_clear_ptr; - - fformat (stdout, - " vectors rates in %.4e, out %.4e, drop %.4e, " - "punt %.4e\n", - (f64) n_input / dt, - (f64) n_output / dt, (f64) n_drop / dt, - (f64) n_punt / dt); - - vec_free (nodes); - } - vec_free (nodes_by_thread); - } - else - { - fformat (stdout, "serialized nodes NULL?\n"); - } - - } -} - -int -main (int argc, char **argv) -{ - unformat_input_t _argv, *a = &_argv; - stat_client_main_t *sm = &stat_client_main; - u8 *stat_segment_name; - int rv; - - clib_mem_init (0, 128 << 20); - - unformat_init_command_line (a, argv); - - stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE; - - while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT) - { - if (unformat (a, "socket-name %s", &stat_segment_name)) - ; - else - { - fformat (stderr, "%s: usage [socket-name <name>]\n", argv[0]); - exit (1); - } - } - - sm->socket_name = stat_segment_name; - - rv = stat_segment_connect (sm); - if (rv) - { - fformat (stderr, "Couldn't connect to vpp, does %s exist?\n", - stat_segment_name); - exit (1); - } - - stat_poll_loop (sm); - exit (0); -} - -/* - * fd.io coding-style-patch-verification: ON - * - * Local Variables: - * eval: (c-set-style "gnu") - * End: - */ diff --git a/src/vpp/app/stat_client.h b/src/vpp/app/stat_client.h deleted file mode 100644 index 97bd3f9bb0b..00000000000 --- a/src/vpp/app/stat_client.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2018 Cisco and/or its affiliates. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef __included_stat_client_h__ -#define __included_stat_client_h__ - -#include <vlib/vlib.h> -#include <vppinfra/socket.h> -#include <svm/ssvm.h> -#include <vpp/stats/stats.h> - -typedef struct -{ - u64 current_epoch; - - /* Cached pointers to scalar quantities, these wont change */ - f64 *vector_rate_ptr; - f64 *input_rate_ptr; - f64 *last_runtime_ptr; - f64 *last_runtime_stats_clear_ptr; - - volatile int segment_ready; - - /* - * Cached pointers to vector quantities, - * MUST invalidate when the epoch changes - */ - vlib_counter_t **intfc_rx_counters; - vlib_counter_t **intfc_tx_counters; - u8 *serialized_nodes; - - u64 *thread_0_error_counts; - u64 source_address_match_error_index; - - /* mapped stats segment object */ - ssvm_private_t stat_segment; - - /* Spinlock for the stats segment */ - clib_spinlock_t *stat_segment_lockp; - - u8 *socket_name; -} stat_client_main_t; - -extern stat_client_main_t stat_client_main; - -#endif /* __included_stat_client_h__ */ - - -/* - * fd.io coding-style-patch-verification: ON - * - * Local Variables: - * eval: (c-set-style "gnu") - * End: - */ diff --git a/src/vpp/app/vpp_get_stats.c b/src/vpp/app/vpp_get_stats.c new file mode 100644 index 00000000000..86e511e56f2 --- /dev/null +++ b/src/vpp/app/vpp_get_stats.c @@ -0,0 +1,242 @@ +/* + *------------------------------------------------------------------ + * vpp_get_stats.c + * + * Copyright (c) 2018 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#include <vpp-api/client/stat_client.h> +#include <vlib/vlib.h> +#include <vpp/stats/stats.h> + +static int +stat_poll_loop (stat_segment_cached_pointer_t * cp) +{ + struct timespec ts, tsrem; + stat_segment_data_t *res; + int i, j, k, lost_connection = 0; + f64 heartbeat, prev_heartbeat = 0; + + printf ("\033[2J"); /* clear the screen */ + while (1) + { + heartbeat = stat_segment_heartbeat (); + if (heartbeat > prev_heartbeat) + { + prev_heartbeat = heartbeat; + lost_connection = 0; + } + else + { + lost_connection++; + } + if (lost_connection > 10) + { + fformat (stderr, "Lost connection to VPP...\n"); + return -1; + } + + printf ("\033[H"); /* Cursor top left corner */ + res = stat_segment_collect (cp); + for (i = 0; i < vec_len (res); i++) + { + switch (res[i].type) + { + case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE: + for (k = 0; k < vec_len (res[i].simple_counter_vec); k++) + for (j = 0; j < vec_len (res[i].simple_counter_vec[k]); j++) + fformat (stdout, "[%d]: %lld packets %s\n", + j, res[i].simple_counter_vec[k][j], res[i].name); + break; + + case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED: + for (k = 0; k < vec_len (res[i].simple_counter_vec); k++) + for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++) + fformat (stdout, "[%d]: %lld packets, %lld bytes %s\n", + j, res[i].combined_counter_vec[k][j].packets, + res[i].combined_counter_vec[k][j].bytes, + res[i].name); + break; + + case STAT_DIR_TYPE_ERROR_INDEX: + fformat (stdout, "%lld %s\n", res[i].error_value, res[i].name); + break; + + case STAT_DIR_TYPE_SCALAR_POINTER: + fformat (stdout, "%.2f %s\n", res[i].scalar_value, res[i].name); + break; + + default: + printf ("Unknown value\n"); + ; + } + } + stat_segment_data_free (res); + /* Scrape stats every 5 seconds */ + ts.tv_sec = 1; + ts.tv_nsec = 0; + while (nanosleep (&ts, &tsrem) < 0) + ts = tsrem; + + } +} + +enum stat_client_cmd_e +{ + STAT_CLIENT_CMD_UNKNOWN, + STAT_CLIENT_CMD_LS, + STAT_CLIENT_CMD_POLL, + STAT_CLIENT_CMD_DUMP, +}; + +int +main (int argc, char **argv) +{ + unformat_input_t _argv, *a = &_argv; + u8 *stat_segment_name, *pattern = 0, **patterns = 0; + int rv; + enum stat_client_cmd_e cmd = STAT_CLIENT_CMD_UNKNOWN; + + clib_mem_init (0, 128 << 20); + + unformat_init_command_line (a, argv); + + stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE; + + while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT) + { + if (unformat (a, "socket-name %s", &stat_segment_name)) + ; + else if (unformat (a, "ls")) + { + cmd = STAT_CLIENT_CMD_LS; + } + else if (unformat (a, "dump")) + { + cmd = STAT_CLIENT_CMD_DUMP; + } + else if (unformat (a, "poll")) + { + cmd = STAT_CLIENT_CMD_POLL; + } + else if (unformat (a, "%s", &pattern)) + { + vec_add1 (patterns, pattern); + } + else + { + fformat (stderr, + "%s: usage [socket-name <name>] [ls|dump|poll] <patterns> ...\n", + argv[0]); + exit (1); + } + } +reconnect: + rv = stat_segment_connect ((char *) stat_segment_name); + if (rv) + { + fformat (stderr, "Couldn't connect to vpp, does %s exist?\n", + stat_segment_name); + exit (1); + } + + u8 **dir; + int i, j, k; + stat_segment_data_t *res; + stat_segment_cached_pointer_t *cp; + + dir = stat_segment_ls (patterns); + + switch (cmd) + { + case STAT_CLIENT_CMD_LS: + /* List all counters */ + for (i = 0; i < vec_len (dir); i++) + { + printf ("%s\n", (char *) dir[i]); + } + break; + + case STAT_CLIENT_CMD_DUMP: + res = stat_segment_dump (dir); + for (i = 0; i < vec_len (res); i++) + { + switch (res[i].type) + { + case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE: + for (k = 0; k < vec_len (res[i].simple_counter_vec) - 1; k++) + for (j = 0; j < vec_len (res[i].simple_counter_vec[k]); j++) + fformat (stdout, "[%d @ %d]: %lld packets %s\n", + j, k, res[i].simple_counter_vec[k][j], + res[i].name); + break; + + case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED: + for (k = 0; k < vec_len (res[i].simple_counter_vec); k++) + for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++) + fformat (stdout, "[%d @ %d]: %lld packets, %lld bytes %s\n", + j, k, res[i].combined_counter_vec[k][j].packets, + res[i].combined_counter_vec[k][j].bytes, + res[i].name); + break; + + case STAT_DIR_TYPE_ERROR_INDEX: + fformat (stdout, "%lld %s\n", res[i].error_value, dir[i]); + break; + + case STAT_DIR_TYPE_SCALAR_POINTER: + fformat (stdout, "%.2f %s\n", dir[i], res[i].scalar_value, + res[i].name); + break; + + default: + ; + } + } + stat_segment_data_free (res); + break; + + case STAT_CLIENT_CMD_POLL: + cp = stat_segment_register (dir); + if (!cp) + { + fformat (stderr, + "Couldn't register required counters with stat segment\n"); + exit (1); + } + stat_poll_loop (cp); + /* We can only exist the pool loop if we lost connection to VPP */ + stat_segment_disconnect (); + goto reconnect; + break; + + default: + fformat (stderr, + "%s: usage [socket-name <name>] [ls|dump|poll] <patterns> ...\n", + argv[0]); + } + + stat_segment_disconnect (); + + exit (0); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp/app/vpp_prometheus_export.c b/src/vpp/app/vpp_prometheus_export.c new file mode 100644 index 00000000000..57c65178c73 --- /dev/null +++ b/src/vpp/app/vpp_prometheus_export.c @@ -0,0 +1,320 @@ +/* + *------------------------------------------------------------------ + * vpp_get_stats.c + * + * Copyright (c) 2018 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#include <arpa/inet.h> +#include <sys/epoll.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <netdb.h> +#include <sys/socket.h> +#include <vpp-api/client/stat_client.h> +#include <vlib/vlib.h> +#include <vpp/stats/stats.h> +#include <ctype.h> + +/* https://github.com/prometheus/prometheus/wiki/Default-port-allocations */ +#define SERVER_PORT 9477 + +static char * +prom_string (char *s) +{ + char *p = s; + while (*p) + { + if (!isalnum (*p)) + *p = '_'; + p++; + } + return s; +} + +static void +dump_metrics (FILE * stream, stat_segment_cached_pointer_t * cp) +{ + stat_segment_data_t *res; + int i, j, k; + + res = stat_segment_collect (cp); + for (i = 0; i < vec_len (res); i++) + { + switch (res[i].type) + { + case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE: + fformat (stream, "# TYPE %s counter\n", prom_string (res[i].name)); + for (k = 0; k < vec_len (res[i].simple_counter_vec); k++) + for (j = 0; j < vec_len (res[i].simple_counter_vec[k]); j++) + fformat (stream, "%s{thread=\"%d\",interface=\"%d\"} %lld\n", + prom_string (res[i].name), k, j, + res[i].simple_counter_vec[k][j]); + break; + + case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED: + fformat (stream, "# TYPE %s_packets counter\n", + prom_string (res[i].name)); + fformat (stream, "# TYPE %s_bytes counter\n", + prom_string (res[i].name)); + for (k = 0; k < vec_len (res[i].simple_counter_vec); k++) + for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++) + { + fformat (stream, + "%s_packets{thread=\"%d\",interface=\"%d\"} %lld\n", + prom_string (res[i].name), k, j, + res[i].combined_counter_vec[k][j].packets); + fformat (stream, + "%s_bytes{thread=\"%d\",interface=\"%d\"} %lld\n", + prom_string (res[i].name), k, j, + res[i].combined_counter_vec[k][j].bytes); + } + break; + case STAT_DIR_TYPE_ERROR_INDEX: + fformat (stream, "# TYPE %s counter\n", prom_string (res[i].name)); + fformat (stream, "%s{thread=\"0\"} %lld\n", + prom_string (res[i].name), res[i].error_value); + break; + + case STAT_DIR_TYPE_SCALAR_POINTER: + fformat (stream, "# TYPE %s counter\n", prom_string (res[i].name)); + fformat (stream, "%s %.2f\n", prom_string (res[i].name), + res[i].scalar_value); + break; + + default: + fformat (stderr, "Unknown value %d\n", res[i].type); + ; + } + } + stat_segment_data_free (res); + +} + + +#define ROOTPAGE "<html><head><title>Metrics exporter</title></head><body><ul><li><a href=\"/metrics\">metrics</a></li></ul></body></html>" +#define NOT_FOUND_ERROR "<html><head><title>Document not found</title></head><body><h1>404 - Document not found</h1></body></html>" + +static void +http_handler (FILE * stream, stat_segment_cached_pointer_t * cp) +{ + char status[80] = { 0 }; + if (fgets (status, sizeof (status) - 1, stream) == 0) + { + fprintf (stderr, "fgets error: %s %s\n", status, strerror (errno)); + return; + } + char *saveptr; + char *method = strtok_r (status, " \t\r\n", &saveptr); + if (method == 0 || strncmp (method, "GET", 4) != 0) + { + fputs ("HTTP/1.0 405 Method Not Allowed\r\n", stream); + return; + } + char *request_uri = strtok_r (NULL, " \t", &saveptr); + char *protocol = strtok_r (NULL, " \t\r\n", &saveptr); + if (protocol == 0 || strncmp (protocol, "HTTP/1.", 7) != 0) + { + fputs ("HTTP/1.0 400 Bad Request\r\n", stream); + return; + } + /* Read the other headers */ + for (;;) + { + char header[1024]; + if (fgets (header, sizeof (header) - 1, stream) == 0) + { + fprintf (stderr, "fgets error: %s\n", strerror (errno)); + return; + } + if (header[0] == '\n' || header[1] == '\n') + { + break; + } + } + if (strcmp (request_uri, "/") == 0) + { + fprintf (stream, "HTTP/1.0 200 OK\r\nContent-Length: %lu\r\n\r\n", + (unsigned long) strlen (ROOTPAGE)); + fputs (ROOTPAGE, stream); + return; + } + if (strcmp (request_uri, "/metrics") != 0) + { + fprintf (stream, + "HTTP/1.0 404 Not Found\r\nContent-Length: %lu\r\n\r\n", + (unsigned long) strlen (NOT_FOUND_ERROR)); + fputs (NOT_FOUND_ERROR, stream); + return; + } + fputs ("HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n", stream); + dump_metrics (stream, cp); +} + +static int +start_listen (u16 port) +{ + struct sockaddr_in6 serveraddr; + int addrlen = sizeof (serveraddr); + int enable = 1; + + int listenfd = socket (AF_INET6, SOCK_STREAM, 0); + if (listenfd == -1) + { + perror ("Failed opening socket"); + } + + int rv = + setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof (int)); + if (rv < 0) + { + perror ("Failed setsockopt"); + } + + memset (&serveraddr, 0, sizeof (serveraddr)); + serveraddr.sin6_family = AF_INET6; + serveraddr.sin6_port = htons (port); + serveraddr.sin6_addr = in6addr_any; + + if (bind (listenfd, (struct sockaddr *) &serveraddr, addrlen) < 0) + { + fprintf (stderr, "bind() error %s\n", strerror (errno)); + return -1; + } + if (listen (listenfd, 1000000) != 0) + { + fprintf (stderr, "listen() error for %s\n", strerror (errno)); + return -1; + } + return listenfd; +} + +/* Socket epoll, linux-specific */ +union my_sockaddr +{ + struct sockaddr_storage storage; + struct sockaddr addr; + struct sockaddr_in sin_addr; + struct sockaddr_in6 sin6_addr; +}; + + + +int +main (int argc, char **argv) +{ + unformat_input_t _argv, *a = &_argv; + u8 *stat_segment_name, *pattern = 0, **patterns = 0; + int rv; + + clib_mem_init (0, 128 << 20); + + unformat_init_command_line (a, argv); + + stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE; + + while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT) + { + if (unformat (a, "socket-name %s", &stat_segment_name)) + ; + else if (unformat (a, "%s", &pattern)) + { + vec_add1 (patterns, pattern); + } + else + { + fformat (stderr, + "%s: usage [socket-name <name>] <patterns> ...\n", + argv[0]); + exit (1); + } + } + + rv = stat_segment_connect ((char *) stat_segment_name); + if (rv) + { + fformat (stderr, "Couldn't connect to vpp, does %s exist?\n", + stat_segment_name); + exit (1); + } + + u8 **dir; + stat_segment_cached_pointer_t *cp; + + dir = stat_segment_ls (patterns); + cp = stat_segment_register (dir); + if (!cp) + { + fformat (stderr, + "Couldn't register required counters with stat segment\n"); + exit (1); + } + + int fd = start_listen (SERVER_PORT); + if (fd < 0) + { + exit (1); + } + for (;;) + { + int conn_sock = accept (fd, NULL, NULL); + if (conn_sock < 0) + { + fprintf (stderr, "Accept failed: %s", strerror (errno)); + continue; + } + else + { + struct sockaddr_in6 clientaddr; + char address[INET6_ADDRSTRLEN]; + socklen_t addrlen; + getpeername (conn_sock, (struct sockaddr *) &clientaddr, &addrlen); + if (inet_ntop + (AF_INET6, &clientaddr.sin6_addr, address, sizeof (address))) + { + printf ("Client address is [%s]:%d\n", address, + ntohs (clientaddr.sin6_port)); + } + } + + FILE *stream = fdopen (conn_sock, "r+"); + if (stream == NULL) + { + fprintf (stderr, "fdopen error: %s\n", strerror (errno)); + close (conn_sock); + continue; + } + /* Single reader at the moment */ + http_handler (stream, cp); + fclose (stream); + } + + stat_segment_disconnect (); + + exit (0); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp/stats/stat_segment.c b/src/vpp/stats/stat_segment.c index 29b3bf3812d..8459138be34 100644 --- a/src/vpp/stats/stat_segment.c +++ b/src/vpp/stats/stat_segment.c @@ -43,7 +43,7 @@ vlib_stats_push_heap (void) } void -vlib_stats_pop_heap (void *cm_arg, void *oldheap) +vlib_stats_pop_heap (void *cm_arg, void *oldheap, stat_directory_type_t type) { vlib_simple_counter_main_t *cm = (vlib_simple_counter_main_t *) cm_arg; stats_main_t *sm = &stats_main; @@ -80,7 +80,7 @@ vlib_stats_pop_heap (void *cm_arg, void *oldheap) } name_copy = format (0, "%s%c", stat_segment_name, 0); ep = clib_mem_alloc (sizeof (*ep)); - ep->type = STAT_DIR_TYPE_COUNTER_VECTOR; + ep->type = type; ep->value = cm->counters; hash_set_mem (sm->counter_vector_by_name, name_copy, ep); @@ -236,6 +236,7 @@ vlib_map_stat_segment_init (void) sm->input_rate_ptr = (scalar_data + 1); sm->last_runtime_ptr = (scalar_data + 2); sm->last_runtime_stats_clear_ptr = (scalar_data + 3); + sm->heartbeat_ptr = (scalar_data + 4); name = format (0, "/sys/vector_rate%c", 0); ep = clib_mem_alloc (sizeof (*ep)); @@ -265,6 +266,13 @@ vlib_map_stat_segment_init (void) hash_set_mem (sm->counter_vector_by_name, name, ep); + name = format (0, "/sys/heartbeat%c", 0); + ep = clib_mem_alloc (sizeof (*ep)); + ep->type = STAT_DIR_TYPE_SCALAR_POINTER; + ep->value = sm->heartbeat_ptr; + + hash_set_mem (sm->counter_vector_by_name, name, ep); + /* Publish the hash table */ shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR] = sm->counter_vector_by_name; @@ -309,7 +317,8 @@ format_stat_dir_entry (u8 * s, va_list * args) type_name = "VectorPtr"; break; - case STAT_DIR_TYPE_COUNTER_VECTOR: + case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE: + case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED: type_name = "CMainPtr"; break; @@ -512,6 +521,9 @@ do_stat_segment_updates (stats_main_t * sm) if (sm->serialize_nodes) update_serialized_nodes (sm); + + /* Heartbeat, so clients detect we're still here */ + (*sm->heartbeat_ptr)++; } static clib_error_t * diff --git a/src/vpp/stats/stats.h b/src/vpp/stats/stats.h index aea2da7a2c2..911706cbb09 100644 --- a/src/vpp/stats/stats.h +++ b/src/vpp/stats/stats.h @@ -175,6 +175,7 @@ typedef struct f64 *last_runtime_ptr; f64 *last_runtime_stats_clear_ptr; f64 *vector_rate_ptr; + f64 *heartbeat_ptr; u64 last_input_packets; /* Pointers to vector stats maintained by the stat thread */ @@ -209,7 +210,8 @@ typedef enum STAT_DIR_TYPE_ILLEGAL = 0, STAT_DIR_TYPE_SCALAR_POINTER, STAT_DIR_TYPE_VECTOR_POINTER, - STAT_DIR_TYPE_COUNTER_VECTOR, + STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE, + STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED, STAT_DIR_TYPE_ERROR_INDEX, STAT_DIR_TYPE_SERIALIZED_NODES, } stat_directory_type_t; |