aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS5
-rw-r--r--src/plugins/http_static/static_server.c4
-rw-r--r--src/plugins/prom/CMakeLists.txt21
-rw-r--r--src/plugins/prom/FEATURE.yaml10
-rw-r--r--src/plugins/prom/prom.c417
-rw-r--r--src/plugins/prom/prom.h58
-rw-r--r--src/plugins/prom/prom_cli.c147
7 files changed, 662 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 9081a360430..ec6987386f2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -779,6 +779,11 @@ I: http
M: Florin Coras <fcoras@cisco.com>
F: src/plugins/http
+Plugin - Prom
+I: prom
+M: Florin Coras <fcoras@cisco.com>
+F: src/plugins/prom
+
cJSON
I: cjson
M: Ole Troan <ot@cisco.com>
diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c
index a9b3fdbb08a..bd231b44b47 100644
--- a/src/plugins/http_static/static_server.c
+++ b/src/plugins/http_static/static_server.c
@@ -300,6 +300,10 @@ hss_session_send_data (hss_url_handler_args_t *args)
hss_session_t *hs;
hs = hss_session_get (args->sh.thread_index, args->sh.session_index);
+
+ if (hs->data && hs->free_data)
+ vec_free (hs->data);
+
hs->data = args->data;
hs->data_len = args->data_len;
hs->free_data = args->free_vec_data;
diff --git a/src/plugins/prom/CMakeLists.txt b/src/plugins/prom/CMakeLists.txt
new file mode 100644
index 00000000000..6c1976c74f3
--- /dev/null
+++ b/src/plugins/prom/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Copyright (c) 2022 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.
+
+add_vpp_plugin(prom
+ SOURCES
+ prom.c
+ prom_cli.c
+
+ LINK_LIBRARIES
+ vppapiclient
+)
diff --git a/src/plugins/prom/FEATURE.yaml b/src/plugins/prom/FEATURE.yaml
new file mode 100644
index 00000000000..65fefa7f177
--- /dev/null
+++ b/src/plugins/prom/FEATURE.yaml
@@ -0,0 +1,10 @@
+---
+name: Prom (Prometheus Exporter)
+maintainer: Florin Coras <fcoras@cisco.com>
+features:
+ - Stats scraper
+ - Prometheus exporter
+description: "HTTP static server url handler that scrapes stats and exports
+ them in Prometheus format"
+state: experimental
+properties: [MULTITHREAD]
diff --git a/src/plugins/prom/prom.c b/src/plugins/prom/prom.c
new file mode 100644
index 00000000000..77b739a821b
--- /dev/null
+++ b/src/plugins/prom/prom.c
@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2022 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 <vnet/plugin/plugin.h>
+#include <vpp/app/version.h>
+
+#include <prom/prom.h>
+#include <vpp-api/client/stat_client.h>
+#include <vpp/stats/stat_segment.h>
+#include <ctype.h>
+
+static prom_main_t prom_main;
+
+static char *
+fix_spaces (char *s)
+{
+ char *p = s;
+ while (*p)
+ {
+ if (!isalnum (*p))
+ *p = '_';
+ p++;
+ }
+ return s;
+}
+
+static u8 *
+dump_counter_vector_simple (stat_segment_data_t *res, u8 *s, u8 used_only)
+{
+ u8 need_header = 1;
+ int j, k;
+
+ for (k = 0; k < vec_len (res->simple_counter_vec); k++)
+ for (j = 0; j < vec_len (res->simple_counter_vec[k]); j++)
+ {
+ if (used_only && !res->simple_counter_vec[k][j])
+ continue;
+ if (need_header)
+ {
+ s = format (s, "# TYPE %s counter\n", fix_spaces (res->name));
+ need_header = 0;
+ }
+ s =
+ format (s, "%s{thread=\"%d\",interface=\"%d\"} %lld\n",
+ fix_spaces (res->name), k, j, res->simple_counter_vec[k][j]);
+ }
+ return s;
+}
+
+static u8 *
+dump_counter_vector_combined (stat_segment_data_t *res, u8 *s, u8 used_only)
+{
+ u8 need_header = 1;
+ int j, k;
+
+ for (k = 0; k < vec_len (res->simple_counter_vec); k++)
+ for (j = 0; j < vec_len (res->combined_counter_vec[k]); j++)
+ {
+ if (used_only && !res->combined_counter_vec[k][j].packets)
+ continue;
+ if (need_header)
+ {
+ s = format (s, "# TYPE %s_packets counter\n",
+ fix_spaces (res->name));
+ s =
+ format (s, "# TYPE %s_bytes counter\n", fix_spaces (res->name));
+ need_header = 0;
+ }
+ s = format (s, "%s_packets{thread=\"%d\",interface=\"%d\"} %lld\n",
+ fix_spaces (res->name), k, j,
+ res->combined_counter_vec[k][j].packets);
+ s = format (s, "%s_bytes{thread=\"%d\",interface=\"%d\"} %lld\n",
+ fix_spaces (res->name), k, j,
+ res->combined_counter_vec[k][j].bytes);
+ }
+
+ return s;
+}
+
+static u8 *
+dump_error_index (stat_segment_data_t *res, u8 *s, u8 used_only)
+{
+ int j;
+
+ for (j = 0; j < vec_len (res->error_vector); j++)
+ {
+ if (used_only && !res->error_vector[j])
+ continue;
+ s = format (s, "# TYPE %s counter\n", fix_spaces (res->name));
+ s = format (s, "%s{thread=\"%d\"} %lld\n", fix_spaces (res->name), j,
+ res->error_vector[j]);
+ }
+
+ return s;
+}
+
+static u8 *
+dump_scalar_index (stat_segment_data_t *res, u8 *s, u8 used_only)
+{
+ if (used_only && !res->scalar_value)
+ return s;
+
+ s = format (s, "# TYPE %s counter\n", fix_spaces (res->name));
+ s = format (s, "%s %.2f\n", fix_spaces (res->name), res->scalar_value);
+
+ return s;
+}
+
+static u8 *
+dump_name_vector (stat_segment_data_t *res, u8 *s, u8 used_only)
+{
+ int k;
+
+ s = format (s, "# TYPE %s_info gauge\n", fix_spaces (res->name));
+ for (k = 0; k < vec_len (res->name_vector); k++)
+ s = format (s, "%s_info{index=\"%d\",name=\"%s\"} 1\n",
+ fix_spaces (res->name), k, res->name_vector[k]);
+
+ return s;
+}
+
+static u8 *
+scrape_stats_segment (u8 *s, u8 **patterns, u8 used_only)
+{
+ stat_segment_data_t *res;
+ static u32 *stats = 0;
+ int i;
+
+ stats = stat_segment_ls (patterns);
+
+retry:
+ res = stat_segment_dump (stats);
+ if (res == 0)
+ { /* Memory layout has changed */
+ if (stats)
+ vec_free (stats);
+ stats = stat_segment_ls (patterns);
+ goto retry;
+ }
+
+ for (i = 0; i < vec_len (res); i++)
+ {
+ switch (res[i].type)
+ {
+ case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
+ s = dump_counter_vector_simple (&res[i], s, used_only);
+ break;
+
+ case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
+ s = dump_counter_vector_combined (&res[i], s, used_only);
+ break;
+ case STAT_DIR_TYPE_ERROR_INDEX:
+ s = dump_error_index (&res[i], s, used_only);
+ break;
+
+ case STAT_DIR_TYPE_SCALAR_INDEX:
+ s = dump_scalar_index (&res[i], s, used_only);
+ break;
+
+ case STAT_DIR_TYPE_NAME_VECTOR:
+ s = dump_name_vector (&res[i], s, used_only);
+ break;
+
+ case STAT_DIR_TYPE_EMPTY:
+ break;
+
+ default:
+ clib_warning ("Unknown value %d\n", res[i].type);
+ ;
+ }
+ }
+ stat_segment_data_free (res);
+
+ return s;
+}
+
+static void
+send_data_to_hss (hss_session_handle_t sh)
+{
+ hss_url_handler_args_t args = {};
+ prom_main_t *pm = &prom_main;
+
+ args.sh = sh;
+ args.data = vec_dup (pm->stats);
+ args.data_len = vec_len (pm->stats);
+ args.sc = HTTP_STATUS_OK;
+ args.free_vec_data = 1;
+
+ pm->send_data (&args);
+}
+
+static void
+send_data_to_hss_rpc (void *rpc_args)
+{
+ send_data_to_hss (*(hss_session_handle_t *) rpc_args);
+}
+
+static uword
+prom_scraper_process (vlib_main_t *vm, vlib_node_runtime_t *rt,
+ vlib_frame_t *f)
+{
+ uword *event_data = 0, event_type;
+ prom_main_t *pm = &prom_main;
+ hss_session_handle_t sh;
+ f64 timeout = 10000.0;
+
+ while (1)
+ {
+ vlib_process_wait_for_event_or_clock (vm, timeout);
+ event_type = vlib_process_get_events (vm, (uword **) &event_data);
+ switch (event_type)
+ {
+ case ~0:
+ /* timeout, do nothing */
+ break;
+ case PROM_SCRAPER_EVT_RUN:
+ sh.as_u64 = event_data[0];
+ vec_reset_length (pm->stats);
+ pm->stats = scrape_stats_segment (pm->stats, pm->stats_patterns,
+ pm->used_only);
+ session_send_rpc_evt_to_thread_force (sh.thread_index,
+ send_data_to_hss_rpc, &sh);
+ pm->last_scrape = vlib_time_now (vm);
+ break;
+ default:
+ clib_warning ("unexpected event %u", event_type);
+ break;
+ }
+
+ vec_reset_length (event_data);
+ }
+ return 0;
+}
+
+VLIB_REGISTER_NODE (prom_scraper_process_node) = {
+ .function = prom_scraper_process,
+ .type = VLIB_NODE_TYPE_PROCESS,
+ .name = "prom-scraper-process",
+ .state = VLIB_NODE_STATE_DISABLED,
+};
+
+static void
+prom_scraper_process_enable (vlib_main_t *vm)
+{
+ prom_main_t *pm = &prom_main;
+ vlib_node_t *n;
+
+ vlib_node_set_state (vm, prom_scraper_process_node.index,
+ VLIB_NODE_STATE_POLLING);
+ n = vlib_get_node (vm, prom_scraper_process_node.index);
+ vlib_start_process (vm, n->runtime_index);
+
+ pm->scraper_node_index = n->index;
+}
+
+static void
+signal_run_to_scraper (uword *args)
+{
+ prom_main_t *pm = &prom_main;
+ ASSERT (vlib_get_thread_index () == 0);
+ vlib_process_signal_event (pm->vm, pm->scraper_node_index,
+ PROM_SCRAPER_EVT_RUN, *args);
+}
+
+hss_url_handler_rc_t
+prom_stats_dump (hss_url_handler_args_t *args)
+{
+ vlib_main_t *vm = vlib_get_main ();
+ f64 now = vlib_time_now (vm);
+ prom_main_t *pm = &prom_main;
+
+ /* If we've recently scraped stats, return data */
+ if ((now - pm->last_scrape) < pm->min_scrape_interval)
+ {
+ send_data_to_hss (args->sh);
+ return HSS_URL_HANDLER_ASYNC;
+ }
+
+ if (vm->thread_index != 0)
+ vl_api_rpc_call_main_thread (signal_run_to_scraper, (u8 *) &args->sh,
+ sizeof (args->sh));
+ else
+ signal_run_to_scraper (&args->sh.as_u64);
+
+ return HSS_URL_HANDLER_ASYNC;
+}
+
+void
+prom_stat_patterns_add (u8 **patterns)
+{
+ prom_main_t *pm = &prom_main;
+
+ u8 **pattern, **existing;
+ u8 found;
+ u32 len;
+
+ vec_foreach (pattern, patterns)
+ {
+ found = 0;
+ len = vec_len (*pattern);
+ vec_foreach (existing, pm->stats_patterns)
+ {
+ if (vec_len (*existing) != len)
+ continue;
+ if (!memcmp (*existing, *pattern, len - 1))
+ {
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ vec_add1 (pm->stats_patterns, *pattern);
+ }
+}
+
+void
+prom_stat_patterns_free (void)
+{
+ prom_main_t *pm = &prom_main;
+ u8 **pattern;
+
+ vec_foreach (pattern, pm->stats_patterns)
+ vec_free (*pattern);
+ vec_free (pm->stats_patterns);
+}
+
+void
+prom_stat_patterns_set (u8 **patterns)
+{
+ prom_stat_patterns_free ();
+ prom_stat_patterns_add (patterns);
+}
+
+u8 **
+prom_stat_patterns_get (void)
+{
+ return prom_main.stats_patterns;
+}
+
+static void
+prom_stat_segment_client_init (void)
+{
+ stat_client_main_t *scm = &stat_client_main;
+ stat_segment_main_t *sm = &stat_segment_main;
+ uword size;
+
+ size = sm->memory_size ? sm->memory_size : STAT_SEGMENT_DEFAULT_SIZE;
+ scm->memory_size = size;
+ scm->shared_header = sm->shared_header;
+ scm->directory_vector =
+ stat_segment_adjust (scm, (void *) scm->shared_header->directory_vector);
+}
+
+void
+prom_enable (vlib_main_t *vm)
+{
+ prom_main_t *pm = &prom_main;
+
+ pm->register_url = vlib_get_plugin_symbol ("http_static_plugin.so",
+ "hss_register_url_handler");
+ pm->send_data =
+ vlib_get_plugin_symbol ("http_static_plugin.so", "hss_session_send_data");
+ pm->register_url (prom_stats_dump, "stats.prom", HTTP_REQ_GET);
+
+ pm->is_enabled = 1;
+ pm->vm = vm;
+
+ prom_scraper_process_enable (vm);
+ prom_stat_segment_client_init ();
+}
+
+static clib_error_t *
+prom_init (vlib_main_t *vm)
+{
+ prom_main_t *pm = &prom_main;
+
+ pm->is_enabled = 0;
+ pm->min_scrape_interval = 1;
+ pm->used_only = 0;
+
+ return 0;
+}
+
+prom_main_t *
+prom_get_main (void)
+{
+ return &prom_main;
+}
+
+VLIB_INIT_FUNCTION (prom_init) = {
+ .runs_after = VLIB_INITS ("hss_main_init"),
+};
+
+VLIB_PLUGIN_REGISTER () = {
+ .version = VPP_BUILD_VER,
+ .description = "Prometheus Stats Exporter",
+ .default_disabled = 0,
+};
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/prom/prom.h b/src/plugins/prom/prom.h
new file mode 100644
index 00000000000..baacc28a26a
--- /dev/null
+++ b/src/plugins/prom/prom.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2022 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 SRC_PLUGINS_PROM_PROM_H_
+#define SRC_PLUGINS_PROM_PROM_H_
+
+#include <vnet/session/session.h>
+#include <http_static/http_static.h>
+
+typedef struct prom_main_
+{
+ u8 *stats;
+ f64 last_scrape;
+ hss_register_url_fn register_url;
+ hss_session_send_fn send_data;
+ u32 scraper_node_index;
+ u8 is_enabled;
+
+ u8 **stats_patterns;
+ f64 min_scrape_interval;
+ u8 used_only;
+ vlib_main_t *vm;
+} prom_main_t;
+
+typedef enum prom_process_evt_codes_
+{
+ PROM_SCRAPER_EVT_RUN,
+} prom_process_evt_codes_t;
+
+void prom_enable (vlib_main_t *vm);
+prom_main_t *prom_get_main (void);
+
+void prom_stat_patterns_set (u8 **patterns);
+void prom_stat_patterns_add (u8 **patterns);
+u8 **prom_stat_patterns_get (void);
+void prom_stat_patterns_free (void);
+
+#endif /* SRC_PLUGINS_PROM_PROM_H_ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/prom/prom_cli.c b/src/plugins/prom/prom_cli.c
new file mode 100644
index 00000000000..453e8a066fc
--- /dev/null
+++ b/src/plugins/prom/prom_cli.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2022 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 <prom/prom.h>
+
+static uword
+unformat_stats_patterns (unformat_input_t *input, va_list *args)
+{
+ u8 ***patterns = va_arg (*args, u8 ***);
+ u8 *pattern;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%s", &pattern))
+ vec_add1 (*patterns, pattern);
+ else
+ return 0;
+ }
+ return 1;
+}
+
+static clib_error_t *
+prom_patterns_command_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ unformat_input_t _line_input, *line_input = &_line_input;
+ u8 is_clear = 0, is_show = 0, **pattern = 0;
+ clib_error_t *error = 0;
+
+ if (!unformat_user (input, unformat_line_input, line_input))
+ return 0;
+
+ while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (line_input, "show"))
+ is_show = 1;
+ else if (unformat (line_input, "clear"))
+ is_clear = 1;
+ else if (unformat (line_input, "add %U", unformat_stats_patterns,
+ &pattern))
+ {
+ prom_stat_patterns_add (pattern);
+ vec_free (pattern);
+ }
+ else
+ {
+ error = clib_error_return (0, "unknown input `%U'",
+ format_unformat_error, line_input);
+ break;
+ }
+ }
+ unformat_free (line_input);
+
+ if (error)
+ return error;
+
+ if (is_clear)
+ prom_stat_patterns_free ();
+
+ if (is_show)
+ {
+ u8 **patterns = prom_stat_patterns_get ();
+ vec_foreach (pattern, patterns)
+ vlib_cli_output (vm, " %v\n", *pattern);
+ }
+
+ return 0;
+}
+
+VLIB_CLI_COMMAND (prom_patterns_command, static) = {
+ .path = "prom patterns",
+ .short_help = "prom patterns [show] [clear] [add <patterns>...]",
+ .function = prom_patterns_command_fn,
+};
+
+static clib_error_t *
+prom_command_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ unformat_input_t _line_input, *line_input = &_line_input;
+ prom_main_t *pm = prom_get_main ();
+ clib_error_t *error = 0;
+ u8 **patterns = 0;
+ u8 is_enable = 0;
+
+ if (!unformat_user (input, unformat_line_input, line_input))
+ goto no_input;
+
+ while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (line_input, "enable"))
+ is_enable = 1;
+ else if (unformat (line_input, "min-scrape-interval %f",
+ &pm->min_scrape_interval))
+ ;
+ else if (unformat (line_input, "used-only"))
+ pm->used_only = 1;
+ else if (unformat (line_input, "stat-patterns %U",
+ unformat_stats_patterns, &patterns))
+ prom_stat_patterns_set (patterns);
+ else
+ {
+ error = clib_error_return (0, "unknown input `%U'",
+ format_unformat_error, line_input);
+ break;
+ }
+ }
+
+ unformat_free (line_input);
+
+ if (error)
+ return error;
+
+no_input:
+
+ if (is_enable && !pm->is_enabled)
+ prom_enable (vm);
+
+ return 0;
+}
+
+VLIB_CLI_COMMAND (prom_enable_command, static) = {
+ .path = "prom",
+ .short_help = "prom [enable] [min-scrape-interval <n>] [used-only]"
+ "[stat-patterns <patterns>...]",
+ .function = prom_command_fn,
+};
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */