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