prom: basic builtin prometheus stats exporter
[vpp.git] / src / plugins / prom / prom.c
1 /*
2  * Copyright (c) 2022 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <vnet/plugin/plugin.h>
16 #include <vpp/app/version.h>
17
18 #include <prom/prom.h>
19 #include <vpp-api/client/stat_client.h>
20 #include <vpp/stats/stat_segment.h>
21 #include <ctype.h>
22
23 static prom_main_t prom_main;
24
25 static char *
26 fix_spaces (char *s)
27 {
28   char *p = s;
29   while (*p)
30     {
31       if (!isalnum (*p))
32         *p = '_';
33       p++;
34     }
35   return s;
36 }
37
38 static u8 *
39 dump_counter_vector_simple (stat_segment_data_t *res, u8 *s, u8 used_only)
40 {
41   u8 need_header = 1;
42   int j, k;
43
44   for (k = 0; k < vec_len (res->simple_counter_vec); k++)
45     for (j = 0; j < vec_len (res->simple_counter_vec[k]); j++)
46       {
47         if (used_only && !res->simple_counter_vec[k][j])
48           continue;
49         if (need_header)
50           {
51             s = format (s, "# TYPE %s counter\n", fix_spaces (res->name));
52             need_header = 0;
53           }
54         s =
55           format (s, "%s{thread=\"%d\",interface=\"%d\"} %lld\n",
56                   fix_spaces (res->name), k, j, res->simple_counter_vec[k][j]);
57       }
58   return s;
59 }
60
61 static u8 *
62 dump_counter_vector_combined (stat_segment_data_t *res, u8 *s, u8 used_only)
63 {
64   u8 need_header = 1;
65   int j, k;
66
67   for (k = 0; k < vec_len (res->simple_counter_vec); k++)
68     for (j = 0; j < vec_len (res->combined_counter_vec[k]); j++)
69       {
70         if (used_only && !res->combined_counter_vec[k][j].packets)
71           continue;
72         if (need_header)
73           {
74             s = format (s, "# TYPE %s_packets counter\n",
75                         fix_spaces (res->name));
76             s =
77               format (s, "# TYPE %s_bytes counter\n", fix_spaces (res->name));
78             need_header = 0;
79           }
80         s = format (s, "%s_packets{thread=\"%d\",interface=\"%d\"} %lld\n",
81                     fix_spaces (res->name), k, j,
82                     res->combined_counter_vec[k][j].packets);
83         s = format (s, "%s_bytes{thread=\"%d\",interface=\"%d\"} %lld\n",
84                     fix_spaces (res->name), k, j,
85                     res->combined_counter_vec[k][j].bytes);
86       }
87
88   return s;
89 }
90
91 static u8 *
92 dump_error_index (stat_segment_data_t *res, u8 *s, u8 used_only)
93 {
94   int j;
95
96   for (j = 0; j < vec_len (res->error_vector); j++)
97     {
98       if (used_only && !res->error_vector[j])
99         continue;
100       s = format (s, "# TYPE %s counter\n", fix_spaces (res->name));
101       s = format (s, "%s{thread=\"%d\"} %lld\n", fix_spaces (res->name), j,
102                   res->error_vector[j]);
103     }
104
105   return s;
106 }
107
108 static u8 *
109 dump_scalar_index (stat_segment_data_t *res, u8 *s, u8 used_only)
110 {
111   if (used_only && !res->scalar_value)
112     return s;
113
114   s = format (s, "# TYPE %s counter\n", fix_spaces (res->name));
115   s = format (s, "%s %.2f\n", fix_spaces (res->name), res->scalar_value);
116
117   return s;
118 }
119
120 static u8 *
121 dump_name_vector (stat_segment_data_t *res, u8 *s, u8 used_only)
122 {
123   int k;
124
125   s = format (s, "# TYPE %s_info gauge\n", fix_spaces (res->name));
126   for (k = 0; k < vec_len (res->name_vector); k++)
127     s = format (s, "%s_info{index=\"%d\",name=\"%s\"} 1\n",
128                 fix_spaces (res->name), k, res->name_vector[k]);
129
130   return s;
131 }
132
133 static u8 *
134 scrape_stats_segment (u8 *s, u8 **patterns, u8 used_only)
135 {
136   stat_segment_data_t *res;
137   static u32 *stats = 0;
138   int i;
139
140   stats = stat_segment_ls (patterns);
141
142 retry:
143   res = stat_segment_dump (stats);
144   if (res == 0)
145     { /* Memory layout has changed */
146       if (stats)
147         vec_free (stats);
148       stats = stat_segment_ls (patterns);
149       goto retry;
150     }
151
152   for (i = 0; i < vec_len (res); i++)
153     {
154       switch (res[i].type)
155         {
156         case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
157           s = dump_counter_vector_simple (&res[i], s, used_only);
158           break;
159
160         case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
161           s = dump_counter_vector_combined (&res[i], s, used_only);
162           break;
163         case STAT_DIR_TYPE_ERROR_INDEX:
164           s = dump_error_index (&res[i], s, used_only);
165           break;
166
167         case STAT_DIR_TYPE_SCALAR_INDEX:
168           s = dump_scalar_index (&res[i], s, used_only);
169           break;
170
171         case STAT_DIR_TYPE_NAME_VECTOR:
172           s = dump_name_vector (&res[i], s, used_only);
173           break;
174
175         case STAT_DIR_TYPE_EMPTY:
176           break;
177
178         default:
179           clib_warning ("Unknown value %d\n", res[i].type);
180           ;
181         }
182     }
183   stat_segment_data_free (res);
184
185   return s;
186 }
187
188 static void
189 send_data_to_hss (hss_session_handle_t sh)
190 {
191   hss_url_handler_args_t args = {};
192   prom_main_t *pm = &prom_main;
193
194   args.sh = sh;
195   args.data = vec_dup (pm->stats);
196   args.data_len = vec_len (pm->stats);
197   args.sc = HTTP_STATUS_OK;
198   args.free_vec_data = 1;
199
200   pm->send_data (&args);
201 }
202
203 static void
204 send_data_to_hss_rpc (void *rpc_args)
205 {
206   send_data_to_hss (*(hss_session_handle_t *) rpc_args);
207 }
208
209 static uword
210 prom_scraper_process (vlib_main_t *vm, vlib_node_runtime_t *rt,
211                       vlib_frame_t *f)
212 {
213   uword *event_data = 0, event_type;
214   prom_main_t *pm = &prom_main;
215   hss_session_handle_t sh;
216   f64 timeout = 10000.0;
217
218   while (1)
219     {
220       vlib_process_wait_for_event_or_clock (vm, timeout);
221       event_type = vlib_process_get_events (vm, (uword **) &event_data);
222       switch (event_type)
223         {
224         case ~0:
225           /* timeout, do nothing */
226           break;
227         case PROM_SCRAPER_EVT_RUN:
228           sh.as_u64 = event_data[0];
229           vec_reset_length (pm->stats);
230           pm->stats = scrape_stats_segment (pm->stats, pm->stats_patterns,
231                                             pm->used_only);
232           session_send_rpc_evt_to_thread_force (sh.thread_index,
233                                                 send_data_to_hss_rpc, &sh);
234           pm->last_scrape = vlib_time_now (vm);
235           break;
236         default:
237           clib_warning ("unexpected event %u", event_type);
238           break;
239         }
240
241       vec_reset_length (event_data);
242     }
243   return 0;
244 }
245
246 VLIB_REGISTER_NODE (prom_scraper_process_node) = {
247   .function = prom_scraper_process,
248   .type = VLIB_NODE_TYPE_PROCESS,
249   .name = "prom-scraper-process",
250   .state = VLIB_NODE_STATE_DISABLED,
251 };
252
253 static void
254 prom_scraper_process_enable (vlib_main_t *vm)
255 {
256   prom_main_t *pm = &prom_main;
257   vlib_node_t *n;
258
259   vlib_node_set_state (vm, prom_scraper_process_node.index,
260                        VLIB_NODE_STATE_POLLING);
261   n = vlib_get_node (vm, prom_scraper_process_node.index);
262   vlib_start_process (vm, n->runtime_index);
263
264   pm->scraper_node_index = n->index;
265 }
266
267 static void
268 signal_run_to_scraper (uword *args)
269 {
270   prom_main_t *pm = &prom_main;
271   ASSERT (vlib_get_thread_index () == 0);
272   vlib_process_signal_event (pm->vm, pm->scraper_node_index,
273                              PROM_SCRAPER_EVT_RUN, *args);
274 }
275
276 hss_url_handler_rc_t
277 prom_stats_dump (hss_url_handler_args_t *args)
278 {
279   vlib_main_t *vm = vlib_get_main ();
280   f64 now = vlib_time_now (vm);
281   prom_main_t *pm = &prom_main;
282
283   /* If we've recently scraped stats, return data */
284   if ((now - pm->last_scrape) < pm->min_scrape_interval)
285     {
286       send_data_to_hss (args->sh);
287       return HSS_URL_HANDLER_ASYNC;
288     }
289
290   if (vm->thread_index != 0)
291     vl_api_rpc_call_main_thread (signal_run_to_scraper, (u8 *) &args->sh,
292                                  sizeof (args->sh));
293   else
294     signal_run_to_scraper (&args->sh.as_u64);
295
296   return HSS_URL_HANDLER_ASYNC;
297 }
298
299 void
300 prom_stat_patterns_add (u8 **patterns)
301 {
302   prom_main_t *pm = &prom_main;
303
304   u8 **pattern, **existing;
305   u8 found;
306   u32 len;
307
308   vec_foreach (pattern, patterns)
309     {
310       found = 0;
311       len = vec_len (*pattern);
312       vec_foreach (existing, pm->stats_patterns)
313         {
314           if (vec_len (*existing) != len)
315             continue;
316           if (!memcmp (*existing, *pattern, len - 1))
317             {
318               found = 1;
319               break;
320             }
321         }
322       if (!found)
323         vec_add1 (pm->stats_patterns, *pattern);
324     }
325 }
326
327 void
328 prom_stat_patterns_free (void)
329 {
330   prom_main_t *pm = &prom_main;
331   u8 **pattern;
332
333   vec_foreach (pattern, pm->stats_patterns)
334     vec_free (*pattern);
335   vec_free (pm->stats_patterns);
336 }
337
338 void
339 prom_stat_patterns_set (u8 **patterns)
340 {
341   prom_stat_patterns_free ();
342   prom_stat_patterns_add (patterns);
343 }
344
345 u8 **
346 prom_stat_patterns_get (void)
347 {
348   return prom_main.stats_patterns;
349 }
350
351 static void
352 prom_stat_segment_client_init (void)
353 {
354   stat_client_main_t *scm = &stat_client_main;
355   stat_segment_main_t *sm = &stat_segment_main;
356   uword size;
357
358   size = sm->memory_size ? sm->memory_size : STAT_SEGMENT_DEFAULT_SIZE;
359   scm->memory_size = size;
360   scm->shared_header = sm->shared_header;
361   scm->directory_vector =
362     stat_segment_adjust (scm, (void *) scm->shared_header->directory_vector);
363 }
364
365 void
366 prom_enable (vlib_main_t *vm)
367 {
368   prom_main_t *pm = &prom_main;
369
370   pm->register_url = vlib_get_plugin_symbol ("http_static_plugin.so",
371                                              "hss_register_url_handler");
372   pm->send_data =
373     vlib_get_plugin_symbol ("http_static_plugin.so", "hss_session_send_data");
374   pm->register_url (prom_stats_dump, "stats.prom", HTTP_REQ_GET);
375
376   pm->is_enabled = 1;
377   pm->vm = vm;
378
379   prom_scraper_process_enable (vm);
380   prom_stat_segment_client_init ();
381 }
382
383 static clib_error_t *
384 prom_init (vlib_main_t *vm)
385 {
386   prom_main_t *pm = &prom_main;
387
388   pm->is_enabled = 0;
389   pm->min_scrape_interval = 1;
390   pm->used_only = 0;
391
392   return 0;
393 }
394
395 prom_main_t *
396 prom_get_main (void)
397 {
398   return &prom_main;
399 }
400
401 VLIB_INIT_FUNCTION (prom_init) = {
402   .runs_after = VLIB_INITS ("hss_main_init"),
403 };
404
405 VLIB_PLUGIN_REGISTER () = {
406   .version = VPP_BUILD_VER,
407   .description = "Prometheus Stats Exporter",
408   .default_disabled = 0,
409 };
410
411 /*
412  * fd.io coding-style-patch-verification: ON
413  *
414  * Local Variables:
415  * eval: (c-set-style "gnu")
416  * End:
417  */