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