3fb3704a9a088dd7fca9a2d9b358018621685b34
[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 u8 *
26 make_stat_name (char *name)
27 {
28   prom_main_t *pm = &prom_main;
29   char *p = name;
30
31   while (*p)
32     {
33       if (!isalnum (*p))
34         *p = '_';
35       p++;
36     }
37
38   /* Reuse vector, instead of always allocating, when building a name. */
39   vec_reset_length (pm->name_scratch_pad);
40   pm->name_scratch_pad =
41     format (pm->name_scratch_pad, "%v%s", pm->stat_name_prefix, name);
42   return pm->name_scratch_pad;
43 }
44
45 static u8 *
46 dump_counter_vector_simple (stat_segment_data_t *res, u8 *s, u8 used_only)
47 {
48   u8 need_header = 1;
49   int j, k;
50   u8 *name;
51
52   name = make_stat_name (res->name);
53
54   for (k = 0; k < vec_len (res->simple_counter_vec); k++)
55     for (j = 0; j < vec_len (res->simple_counter_vec[k]); j++)
56       {
57         if (used_only && !res->simple_counter_vec[k][j])
58           continue;
59         if (need_header)
60           {
61             s = format (s, "# TYPE %v counter\n", name);
62             need_header = 0;
63           }
64         s = format (s, "%v{thread=\"%d\",interface=\"%d\"} %lld\n", name, k, j,
65                     res->simple_counter_vec[k][j]);
66       }
67
68   return s;
69 }
70
71 static u8 *
72 dump_counter_vector_combined (stat_segment_data_t *res, u8 *s, u8 used_only)
73 {
74   u8 need_header = 1;
75   int j, k;
76   u8 *name;
77
78   name = make_stat_name (res->name);
79
80   for (k = 0; k < vec_len (res->simple_counter_vec); k++)
81     for (j = 0; j < vec_len (res->combined_counter_vec[k]); j++)
82       {
83         if (used_only && !res->combined_counter_vec[k][j].packets)
84           continue;
85         if (need_header)
86           {
87             s = format (s, "# TYPE %v_packets counter\n", name);
88             s = format (s, "# TYPE %v_bytes counter\n", name);
89             need_header = 0;
90           }
91         s = format (s, "%v_packets{thread=\"%d\",interface=\"%d\"} %lld\n",
92                     name, k, j, res->combined_counter_vec[k][j].packets);
93         s = format (s, "%v_bytes{thread=\"%d\",interface=\"%d\"} %lld\n", name,
94                     k, j, res->combined_counter_vec[k][j].bytes);
95       }
96
97   return s;
98 }
99
100 static u8 *
101 dump_error_index (stat_segment_data_t *res, u8 *s, u8 used_only)
102 {
103   u8 *name;
104   int j;
105
106   name = make_stat_name (res->name);
107
108   for (j = 0; j < vec_len (res->error_vector); j++)
109     {
110       if (used_only && !res->error_vector[j])
111         continue;
112       s = format (s, "# TYPE %v counter\n", name);
113       s =
114         format (s, "%v{thread=\"%d\"} %lld\n", name, j, res->error_vector[j]);
115     }
116
117   return s;
118 }
119
120 static u8 *
121 dump_scalar_index (stat_segment_data_t *res, u8 *s, u8 used_only)
122 {
123   u8 *name;
124
125   if (used_only && !res->scalar_value)
126     return s;
127
128   name = make_stat_name (res->name);
129
130   s = format (s, "# TYPE %v counter\n", name);
131   s = format (s, "%v %.2f\n", name, res->scalar_value);
132
133   return s;
134 }
135
136 static u8 *
137 dump_name_vector (stat_segment_data_t *res, u8 *s, u8 used_only)
138 {
139   u8 *name;
140   int k;
141
142   name = make_stat_name (res->name);
143
144   s = format (s, "# TYPE %v_info gauge\n", name);
145   for (k = 0; k < vec_len (res->name_vector); k++)
146     s = format (s, "%v_info{index=\"%d\",name=\"%s\"} 1\n", name, k,
147                 res->name_vector[k]);
148
149   return s;
150 }
151
152 static u8 *
153 scrape_stats_segment (u8 *s, u8 **patterns, u8 used_only)
154 {
155   stat_segment_data_t *res;
156   static u32 *stats = 0;
157   int i;
158
159   stats = stat_segment_ls (patterns);
160
161 retry:
162   res = stat_segment_dump (stats);
163   if (res == 0)
164     { /* Memory layout has changed */
165       if (stats)
166         vec_free (stats);
167       stats = stat_segment_ls (patterns);
168       goto retry;
169     }
170
171   for (i = 0; i < vec_len (res); i++)
172     {
173       switch (res[i].type)
174         {
175         case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
176           s = dump_counter_vector_simple (&res[i], s, used_only);
177           break;
178
179         case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
180           s = dump_counter_vector_combined (&res[i], s, used_only);
181           break;
182         case STAT_DIR_TYPE_ERROR_INDEX:
183           s = dump_error_index (&res[i], s, used_only);
184           break;
185
186         case STAT_DIR_TYPE_SCALAR_INDEX:
187           s = dump_scalar_index (&res[i], s, used_only);
188           break;
189
190         case STAT_DIR_TYPE_NAME_VECTOR:
191           s = dump_name_vector (&res[i], s, used_only);
192           break;
193
194         case STAT_DIR_TYPE_EMPTY:
195           break;
196
197         default:
198           clib_warning ("Unknown value %d\n", res[i].type);
199           ;
200         }
201     }
202   stat_segment_data_free (res);
203
204   return s;
205 }
206
207 static void
208 send_data_to_hss (hss_session_handle_t sh)
209 {
210   hss_url_handler_args_t args = {};
211   prom_main_t *pm = &prom_main;
212
213   args.sh = sh;
214   args.data = vec_dup (pm->stats);
215   args.data_len = vec_len (pm->stats);
216   args.sc = HTTP_STATUS_OK;
217   args.free_vec_data = 1;
218
219   pm->send_data (&args);
220 }
221
222 static void
223 send_data_to_hss_rpc (void *rpc_args)
224 {
225   send_data_to_hss (*(hss_session_handle_t *) rpc_args);
226 }
227
228 static uword
229 prom_scraper_process (vlib_main_t *vm, vlib_node_runtime_t *rt,
230                       vlib_frame_t *f)
231 {
232   uword *event_data = 0, event_type;
233   prom_main_t *pm = &prom_main;
234   hss_session_handle_t sh;
235   f64 timeout = 10000.0;
236
237   while (1)
238     {
239       vlib_process_wait_for_event_or_clock (vm, timeout);
240       event_type = vlib_process_get_events (vm, (uword **) &event_data);
241       switch (event_type)
242         {
243         case ~0:
244           /* timeout, do nothing */
245           break;
246         case PROM_SCRAPER_EVT_RUN:
247           sh.as_u64 = event_data[0];
248           vec_reset_length (pm->stats);
249           pm->stats = scrape_stats_segment (pm->stats, pm->stats_patterns,
250                                             pm->used_only);
251           session_send_rpc_evt_to_thread_force (sh.thread_index,
252                                                 send_data_to_hss_rpc, &sh);
253           pm->last_scrape = vlib_time_now (vm);
254           break;
255         default:
256           clib_warning ("unexpected event %u", event_type);
257           break;
258         }
259
260       vec_reset_length (event_data);
261     }
262   return 0;
263 }
264
265 VLIB_REGISTER_NODE (prom_scraper_process_node) = {
266   .function = prom_scraper_process,
267   .type = VLIB_NODE_TYPE_PROCESS,
268   .name = "prom-scraper-process",
269   .state = VLIB_NODE_STATE_DISABLED,
270 };
271
272 static void
273 prom_scraper_process_enable (vlib_main_t *vm)
274 {
275   prom_main_t *pm = &prom_main;
276   vlib_node_t *n;
277
278   vlib_node_set_state (vm, prom_scraper_process_node.index,
279                        VLIB_NODE_STATE_POLLING);
280   n = vlib_get_node (vm, prom_scraper_process_node.index);
281   vlib_start_process (vm, n->runtime_index);
282
283   pm->scraper_node_index = n->index;
284 }
285
286 static void
287 signal_run_to_scraper (uword *args)
288 {
289   prom_main_t *pm = &prom_main;
290   ASSERT (vlib_get_thread_index () == 0);
291   vlib_process_signal_event (pm->vm, pm->scraper_node_index,
292                              PROM_SCRAPER_EVT_RUN, *args);
293 }
294
295 hss_url_handler_rc_t
296 prom_stats_dump (hss_url_handler_args_t *args)
297 {
298   vlib_main_t *vm = vlib_get_main ();
299   f64 now = vlib_time_now (vm);
300   prom_main_t *pm = &prom_main;
301
302   /* If we've recently scraped stats, return data */
303   if ((now - pm->last_scrape) < pm->min_scrape_interval)
304     {
305       send_data_to_hss (args->sh);
306       return HSS_URL_HANDLER_ASYNC;
307     }
308
309   if (vm->thread_index != 0)
310     vl_api_rpc_call_main_thread (signal_run_to_scraper, (u8 *) &args->sh,
311                                  sizeof (args->sh));
312   else
313     signal_run_to_scraper (&args->sh.as_u64);
314
315   return HSS_URL_HANDLER_ASYNC;
316 }
317
318 void
319 prom_stat_patterns_add (u8 **patterns)
320 {
321   prom_main_t *pm = &prom_main;
322
323   u8 **pattern, **existing;
324   u8 found;
325   u32 len;
326
327   vec_foreach (pattern, patterns)
328     {
329       found = 0;
330       len = vec_len (*pattern);
331       vec_foreach (existing, pm->stats_patterns)
332         {
333           if (vec_len (*existing) != len)
334             continue;
335           if (!memcmp (*existing, *pattern, len - 1))
336             {
337               found = 1;
338               break;
339             }
340         }
341       if (!found)
342         vec_add1 (pm->stats_patterns, *pattern);
343     }
344 }
345
346 void
347 prom_stat_patterns_free (void)
348 {
349   prom_main_t *pm = &prom_main;
350   u8 **pattern;
351
352   vec_foreach (pattern, pm->stats_patterns)
353     vec_free (*pattern);
354   vec_free (pm->stats_patterns);
355 }
356
357 void
358 prom_stat_patterns_set (u8 **patterns)
359 {
360   prom_stat_patterns_free ();
361   prom_stat_patterns_add (patterns);
362 }
363
364 u8 **
365 prom_stat_patterns_get (void)
366 {
367   return prom_main.stats_patterns;
368 }
369
370 void
371 prom_stat_name_prefix_set (u8 *prefix)
372 {
373   prom_main_t *pm = &prom_main;
374
375   vec_free (pm->stat_name_prefix);
376   pm->stat_name_prefix = prefix;
377 }
378
379 void
380 prom_report_used_only (u8 used_only)
381 {
382   prom_main_t *pm = &prom_main;
383
384   pm->used_only = used_only;
385 }
386
387 static void
388 prom_stat_segment_client_init (void)
389 {
390   stat_client_main_t *scm = &stat_client_main;
391   stat_segment_main_t *sm = &stat_segment_main;
392   uword size;
393
394   size = sm->memory_size ? sm->memory_size : STAT_SEGMENT_DEFAULT_SIZE;
395   scm->memory_size = size;
396   scm->shared_header = sm->shared_header;
397   scm->directory_vector =
398     stat_segment_adjust (scm, (void *) scm->shared_header->directory_vector);
399 }
400
401 void
402 prom_enable (vlib_main_t *vm)
403 {
404   prom_main_t *pm = &prom_main;
405
406   pm->register_url = vlib_get_plugin_symbol ("http_static_plugin.so",
407                                              "hss_register_url_handler");
408   pm->send_data =
409     vlib_get_plugin_symbol ("http_static_plugin.so", "hss_session_send_data");
410   pm->register_url (prom_stats_dump, "stats.prom", HTTP_REQ_GET);
411
412   pm->is_enabled = 1;
413   pm->vm = vm;
414   if (!pm->stat_name_prefix)
415     pm->stat_name_prefix = format (0, "vpp");
416
417   prom_scraper_process_enable (vm);
418   prom_stat_segment_client_init ();
419 }
420
421 static clib_error_t *
422 prom_init (vlib_main_t *vm)
423 {
424   prom_main_t *pm = &prom_main;
425
426   pm->is_enabled = 0;
427   pm->min_scrape_interval = 1;
428   pm->used_only = 0;
429   pm->stat_name_prefix = 0;
430
431   return 0;
432 }
433
434 prom_main_t *
435 prom_get_main (void)
436 {
437   return &prom_main;
438 }
439
440 VLIB_INIT_FUNCTION (prom_init) = {
441   .runs_after = VLIB_INITS ("hss_main_init"),
442 };
443
444 VLIB_PLUGIN_REGISTER () = {
445   .version = VPP_BUILD_VER,
446   .description = "Prometheus Stats Exporter",
447   .default_disabled = 0,
448 };
449
450 /*
451  * fd.io coding-style-patch-verification: ON
452  *
453  * Local Variables:
454  * eval: (c-set-style "gnu")
455  * End:
456  */