nat: Include platform specific headers on FreeBSD
[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   vec_free (stats);
181
182   return s;
183 }
184
185 static void
186 send_data_to_hss (hss_session_handle_t sh)
187 {
188   hss_url_handler_args_t args = {};
189   prom_main_t *pm = &prom_main;
190
191   args.sh = sh;
192   args.data = vec_dup (pm->stats);
193   args.data_len = vec_len (pm->stats);
194   args.sc = HTTP_STATUS_OK;
195   args.free_vec_data = 1;
196
197   pm->send_data (&args);
198 }
199
200 static void
201 send_data_to_hss_rpc (void *rpc_args)
202 {
203   send_data_to_hss (*(hss_session_handle_t *) rpc_args);
204 }
205
206 static uword
207 prom_scraper_process (vlib_main_t *vm, vlib_node_runtime_t *rt,
208                       vlib_frame_t *f)
209 {
210   uword *event_data = 0, event_type;
211   prom_main_t *pm = &prom_main;
212   hss_session_handle_t sh;
213   f64 timeout = 10000.0;
214
215   while (1)
216     {
217       vlib_process_wait_for_event_or_clock (vm, timeout);
218       event_type = vlib_process_get_events (vm, (uword **) &event_data);
219       switch (event_type)
220         {
221         case ~0:
222           /* timeout, do nothing */
223           break;
224         case PROM_SCRAPER_EVT_RUN:
225           sh.as_u64 = event_data[0];
226           vec_reset_length (pm->stats);
227           pm->stats = scrape_stats_segment (pm->stats, pm->stats_patterns,
228                                             pm->used_only);
229           session_send_rpc_evt_to_thread_force (sh.thread_index,
230                                                 send_data_to_hss_rpc, &sh);
231           pm->last_scrape = vlib_time_now (vm);
232           break;
233         default:
234           clib_warning ("unexpected event %u", event_type);
235           break;
236         }
237
238       vec_reset_length (event_data);
239     }
240   return 0;
241 }
242
243 VLIB_REGISTER_NODE (prom_scraper_process_node) = {
244   .function = prom_scraper_process,
245   .type = VLIB_NODE_TYPE_PROCESS,
246   .name = "prom-scraper-process",
247   .state = VLIB_NODE_STATE_DISABLED,
248 };
249
250 static void
251 prom_scraper_process_enable (vlib_main_t *vm)
252 {
253   prom_main_t *pm = &prom_main;
254   vlib_node_t *n;
255
256   vlib_node_set_state (vm, prom_scraper_process_node.index,
257                        VLIB_NODE_STATE_POLLING);
258   n = vlib_get_node (vm, prom_scraper_process_node.index);
259   vlib_start_process (vm, n->runtime_index);
260
261   pm->scraper_node_index = n->index;
262 }
263
264 static void
265 signal_run_to_scraper (uword *args)
266 {
267   prom_main_t *pm = &prom_main;
268   ASSERT (vlib_get_thread_index () == 0);
269   vlib_process_signal_event (pm->vm, pm->scraper_node_index,
270                              PROM_SCRAPER_EVT_RUN, *args);
271 }
272
273 hss_url_handler_rc_t
274 prom_stats_dump (hss_url_handler_args_t *args)
275 {
276   vlib_main_t *vm = vlib_get_main ();
277   f64 now = vlib_time_now (vm);
278   prom_main_t *pm = &prom_main;
279
280   /* If we've recently scraped stats, return data */
281   if ((now - pm->last_scrape) < pm->min_scrape_interval)
282     {
283       send_data_to_hss (args->sh);
284       return HSS_URL_HANDLER_ASYNC;
285     }
286
287   if (vm->thread_index != 0)
288     vl_api_rpc_call_main_thread (signal_run_to_scraper, (u8 *) &args->sh,
289                                  sizeof (args->sh));
290   else
291     signal_run_to_scraper (&args->sh.as_u64);
292
293   return HSS_URL_HANDLER_ASYNC;
294 }
295
296 void
297 prom_stat_patterns_add (u8 **patterns)
298 {
299   prom_main_t *pm = &prom_main;
300
301   u8 **pattern, **existing;
302   u8 found;
303   u32 len;
304
305   vec_foreach (pattern, patterns)
306     {
307       found = 0;
308       len = vec_len (*pattern);
309       if (len == 0)
310         continue;
311       vec_foreach (existing, pm->stats_patterns)
312         {
313           if (vec_len (*existing) != len)
314             continue;
315           if (!memcmp (*existing, *pattern, len - 1))
316             {
317               found = 1;
318               break;
319             }
320         }
321       if (!found)
322         vec_add1 (pm->stats_patterns, *pattern);
323     }
324 }
325
326 void
327 prom_stat_patterns_free (void)
328 {
329   prom_main_t *pm = &prom_main;
330   u8 **pattern;
331
332   vec_foreach (pattern, pm->stats_patterns)
333     vec_free (*pattern);
334   vec_free (pm->stats_patterns);
335 }
336
337 void
338 prom_stat_patterns_set (u8 **patterns)
339 {
340   prom_stat_patterns_free ();
341   prom_stat_patterns_add (patterns);
342 }
343
344 u8 **
345 prom_stat_patterns_get (void)
346 {
347   return prom_main.stats_patterns;
348 }
349
350 void
351 prom_stat_name_prefix_set (u8 *prefix)
352 {
353   prom_main_t *pm = &prom_main;
354
355   vec_free (pm->stat_name_prefix);
356   pm->stat_name_prefix = prefix;
357 }
358
359 void
360 prom_report_used_only (u8 used_only)
361 {
362   prom_main_t *pm = &prom_main;
363
364   pm->used_only = used_only;
365 }
366
367 static void
368 prom_stat_segment_client_init (void)
369 {
370   stat_client_main_t *scm = &stat_client_main;
371   vlib_stats_segment_t *sm = vlib_stats_get_segment ();
372   uword size;
373
374   size = sm->memory_size ? sm->memory_size : STAT_SEGMENT_DEFAULT_SIZE;
375   scm->memory_size = size;
376   scm->shared_header = sm->shared_header;
377   scm->directory_vector =
378     stat_segment_adjust (scm, (void *) scm->shared_header->directory_vector);
379 }
380
381 void
382 prom_enable (vlib_main_t *vm)
383 {
384   prom_main_t *pm = &prom_main;
385
386   pm->register_url = vlib_get_plugin_symbol ("http_static_plugin.so",
387                                              "hss_register_url_handler");
388   pm->send_data =
389     vlib_get_plugin_symbol ("http_static_plugin.so", "hss_session_send_data");
390   pm->register_url (prom_stats_dump, "stats.prom", HTTP_REQ_GET);
391
392   pm->is_enabled = 1;
393   pm->vm = vm;
394   if (!pm->stat_name_prefix)
395     pm->stat_name_prefix = format (0, "vpp");
396
397   prom_scraper_process_enable (vm);
398   prom_stat_segment_client_init ();
399 }
400
401 static clib_error_t *
402 prom_init (vlib_main_t *vm)
403 {
404   prom_main_t *pm = &prom_main;
405
406   pm->is_enabled = 0;
407   pm->min_scrape_interval = 1;
408   pm->used_only = 0;
409   pm->stat_name_prefix = 0;
410
411   return 0;
412 }
413
414 prom_main_t *
415 prom_get_main (void)
416 {
417   return &prom_main;
418 }
419
420 VLIB_INIT_FUNCTION (prom_init) = {
421   .runs_after = VLIB_INITS ("hss_main_init"),
422 };
423
424 VLIB_PLUGIN_REGISTER () = {
425   .version = VPP_BUILD_VER,
426   .description = "Prometheus Stats Exporter",
427   .default_disabled = 0,
428 };
429
430 /*
431  * fd.io coding-style-patch-verification: ON
432  *
433  * Local Variables:
434  * eval: (c-set-style "gnu")
435  * End:
436  */