aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/prom/prom.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/prom/prom.c')
-rw-r--r--src/plugins/prom/prom.c436
1 files changed, 436 insertions, 0 deletions
diff --git a/src/plugins/prom/prom.c b/src/plugins/prom/prom.c
new file mode 100644
index 00000000000..934e8480d3c
--- /dev/null
+++ b/src/plugins/prom/prom.c
@@ -0,0 +1,436 @@
+/*
+ * 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 <vlib/stats/stats.h>
+#include <ctype.h>
+
+static prom_main_t prom_main;
+
+static u8 *
+make_stat_name (char *name)
+{
+ prom_main_t *pm = &prom_main;
+ char *p = name;
+
+ while (*p)
+ {
+ if (!isalnum (*p))
+ *p = '_';
+ p++;
+ }
+
+ /* Reuse vector, instead of always allocating, when building a name. */
+ vec_reset_length (pm->name_scratch_pad);
+ pm->name_scratch_pad =
+ format (pm->name_scratch_pad, "%v%s", pm->stat_name_prefix, name);
+ return pm->name_scratch_pad;
+}
+
+static u8 *
+dump_counter_vector_simple (stat_segment_data_t *res, u8 *s, u8 used_only)
+{
+ u8 need_header = 1;
+ int j, k;
+ u8 *name;
+
+ name = make_stat_name (res->name);
+
+ 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 %v counter\n", name);
+ need_header = 0;
+ }
+ s = format (s, "%v{thread=\"%d\",interface=\"%d\"} %lld\n", 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;
+ u8 *name;
+
+ name = make_stat_name (res->name);
+
+ 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 %v_packets counter\n", name);
+ s = format (s, "# TYPE %v_bytes counter\n", name);
+ need_header = 0;
+ }
+ s = format (s, "%v_packets{thread=\"%d\",interface=\"%d\"} %lld\n",
+ name, k, j, res->combined_counter_vec[k][j].packets);
+ s = format (s, "%v_bytes{thread=\"%d\",interface=\"%d\"} %lld\n", name,
+ k, j, res->combined_counter_vec[k][j].bytes);
+ }
+
+ return s;
+}
+
+static u8 *
+dump_scalar_index (stat_segment_data_t *res, u8 *s, u8 used_only)
+{
+ u8 *name;
+
+ if (used_only && !res->scalar_value)
+ return s;
+
+ name = make_stat_name (res->name);
+
+ s = format (s, "# TYPE %v counter\n", name);
+ s = format (s, "%v %.2f\n", name, res->scalar_value);
+
+ return s;
+}
+
+static u8 *
+dump_name_vector (stat_segment_data_t *res, u8 *s, u8 used_only)
+{
+ u8 *name;
+ int k;
+
+ name = make_stat_name (res->name);
+
+ s = format (s, "# TYPE %v_info gauge\n", name);
+ for (k = 0; k < vec_len (res->name_vector); k++)
+ s = format (s, "%v_info{index=\"%d\",name=\"%s\"} 1\n", 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_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);
+ vec_free (stats);
+
+ 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);
+ if (len == 0)
+ continue;
+ 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;
+}
+
+void
+prom_stat_name_prefix_set (u8 *prefix)
+{
+ prom_main_t *pm = &prom_main;
+
+ vec_free (pm->stat_name_prefix);
+ pm->stat_name_prefix = prefix;
+}
+
+void
+prom_report_used_only (u8 used_only)
+{
+ prom_main_t *pm = &prom_main;
+
+ pm->used_only = used_only;
+}
+
+static void
+prom_stat_segment_client_init (void)
+{
+ stat_client_main_t *scm = &stat_client_main;
+ vlib_stats_segment_t *sm = vlib_stats_get_segment ();
+ 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;
+ if (!pm->stat_name_prefix)
+ pm->stat_name_prefix = format (0, "vpp");
+
+ 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;
+ pm->stat_name_prefix = 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:
+ */