1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **/
19
20 #include "common.h"
21 #include "zbxalgo.h"
22 #include "zbxregexp.h"
23 #include "log.h"
24 #include "zbxjson.h"
25 #include "zbxprometheus.h"
26
27 /* Defines maximum row length to be written in error message in the case of parsing failure */
28 #define ZBX_PROMEHTEUS_ERROR_MAX_ROW_LENGTH 50
29
30 #define ZBX_PROMETHEUS_HINT_HELP 0
31 #define ZBX_PROMETHEUS_HINT_TYPE 1
32
33 #define ZBX_PROMETHEUS_TYPE_UNTYPED "untyped"
34
35 #define ZBX_PROMETHEUS_ERROR_ROW_NUM 10
36
37 typedef enum
38 {
39 ZBX_PROMETHEUS_CONDITION_OP_EQUAL,
40 ZBX_PROMETHEUS_CONDITION_OP_REGEX,
41 ZBX_PROMETHEUS_CONDITION_OP_EQUAL_VALUE,
42 }
43 zbx_prometheus_condition_op_t;
44
45 /* key-value matching data */
46 typedef struct
47 {
48 /* the key to match, optional - can be NULL */
49 char *key;
50 /* the pattern to match */
51 char *pattern;
52 /* the condition operations */
53 zbx_prometheus_condition_op_t op;
54 }
55 zbx_prometheus_condition_t;
56
57 /* the prometheus pattern filter */
58 typedef struct
59 {
60 /* metric filter, optional - can be NULL */
61 zbx_prometheus_condition_t *metric;
62 /* value filter, optional - can be NULL */
63 zbx_prometheus_condition_t *value;
64 /* label filters */
65 zbx_vector_ptr_t labels;
66 }
67 zbx_prometheus_filter_t;
68
69 /* the prometheus label */
70 typedef struct
71 {
72 char *name;
73 char *value;
74 }
75 zbx_prometheus_label_t;
76
77 /* the prometheus data row */
78 typedef struct
79 {
80 char *metric;
81 char *value;
82 zbx_vector_ptr_t labels;
83 char *raw;
84 }
85 zbx_prometheus_row_t;
86
87 /* the prometheus metric HELP, TYPE hints in comments */
88 typedef struct
89 {
90 char *metric;
91 char *type;
92 char *help;
93 }
94 zbx_prometheus_hint_t;
95
96 /* TYPE, HELP hint hashset support */
97
prometheus_hint_hash(const void * d)98 static zbx_hash_t prometheus_hint_hash(const void *d)
99 {
100 const zbx_prometheus_hint_t *hint = (zbx_prometheus_hint_t *)d;
101
102 return ZBX_DEFAULT_STRING_HASH_FUNC(hint->metric);
103 }
104
prometheus_hint_compare(const void * d1,const void * d2)105 static int prometheus_hint_compare(const void *d1, const void *d2)
106 {
107 const zbx_prometheus_hint_t *hint1 = (zbx_prometheus_hint_t *)d1;
108 const zbx_prometheus_hint_t *hint2 = (zbx_prometheus_hint_t *)d2;
109
110 return strcmp(hint1->metric, hint2->metric);
111 }
112
113 /******************************************************************************
114 * *
115 * Function: str_loc_dup *
116 * *
117 * Purpose: allocates and copies substring at the specified location *
118 * *
119 * Parameters: src - [IN] the source string *
120 * loc - [IN] the substring location *
121 * *
122 * Return value: The copied substring. *
123 * *
124 ******************************************************************************/
str_loc_dup(const char * src,const zbx_strloc_t * loc)125 static char *str_loc_dup(const char *src, const zbx_strloc_t *loc)
126 {
127 char *str;
128 size_t len;
129
130 len = loc->r - loc->l + 1;
131 str = zbx_malloc(NULL, len + 1);
132 memcpy(str, src + loc->l, len);
133 str[len] = '\0';
134
135 return str;
136 }
137
138 /******************************************************************************
139 * *
140 * Function: str_loc_unquote_dyn *
141 * *
142 * Purpose: unquotes substring at the specified location *
143 * *
144 * Parameters: src - [IN] the source string *
145 * loc - [IN] the substring location *
146 * *
147 * Return value: The unquoted and copied substring. *
148 * *
149 ******************************************************************************/
str_loc_unquote_dyn(const char * src,const zbx_strloc_t * loc)150 static char *str_loc_unquote_dyn(const char *src, const zbx_strloc_t *loc)
151 {
152 char *str, *ptr;
153
154 src += loc->l + 1;
155
156 str = ptr = zbx_malloc(NULL, loc->r - loc->l);
157
158 while ('"' != *src)
159 {
160 if ('\\' == *src)
161 {
162 switch (*(++src))
163 {
164 case '\\':
165 *ptr++ = '\\';
166 break;
167 case 'n':
168 *ptr++ = '\n';
169 break;
170 case '"':
171 *ptr++ = '"';
172 break;
173 }
174 }
175 else
176 *ptr++ = *src;
177 src++;
178 }
179 *ptr = '\0';
180
181 return str;
182 }
183
184 /******************************************************************************
185 * *
186 * Function: str_loc_unescape_hint_dyn *
187 * *
188 * Purpose: unescapes HELP hint *
189 * *
190 * Parameters: src - [IN] the source string *
191 * loc - [IN] the substring location *
192 * *
193 * Return value: The unescaped and copied HELP string. *
194 * *
195 ******************************************************************************/
str_loc_unescape_hint_dyn(const char * src,const zbx_strloc_t * loc)196 static char *str_loc_unescape_hint_dyn(const char *src, const zbx_strloc_t *loc)
197 {
198 char *str, *pout;
199 const char *pin;
200 size_t len;
201
202 len = loc->r - loc->l + 1;
203 str = zbx_malloc(NULL, len + 1);
204
205 for (pout = str, pin = src + loc->l; pin <= src + loc->r; pin++)
206 {
207 if ('\\' == *pin)
208 {
209 pin++;
210 switch (*pin)
211 {
212 case '\\':
213 *pout++ = '\\';
214 break;
215 case 'n':
216 *pout++ = '\n';
217 break;
218 default:
219 THIS_SHOULD_NEVER_HAPPEN;
220 *pout++ = '?';
221 }
222 }
223 else
224 *pout++ = *pin;
225 }
226
227 *pout++ ='\0';
228
229 return str;
230 }
231
232 /******************************************************************************
233 * *
234 * Function: str_loc_cmp *
235 * *
236 * Purpose: compares substring at the specified location with the specified *
237 * text *
238 * *
239 * Parameters: src - [IN] the source string *
240 * loc - [IN] the substring location *
241 * text - [IN] the text to compare with *
242 * text_len - [IN] the text length *
243 * *
244 * Return value: -1 - the substring is less than the specified text *
245 * 0 - the substring is equal to the specified text *
246 * 1 - the substring is greater than the specified text *
247 * *
248 ******************************************************************************/
str_loc_cmp(const char * src,const zbx_strloc_t * loc,const char * text,size_t text_len)249 static int str_loc_cmp(const char *src, const zbx_strloc_t *loc, const char *text, size_t text_len)
250 {
251 ZBX_RETURN_IF_NOT_EQUAL(loc->r - loc->l + 1, text_len);
252 return memcmp(src + loc->l, text, text_len);
253 }
254
255 /******************************************************************************
256 * *
257 * Function: str_loc_op *
258 * *
259 * Purpose: parses condition operation at the specified location *
260 * *
261 * Parameters: src - [IN] the source string *
262 * loc - [IN] the substring location *
263 * *
264 * Return value: The condition operation. *
265 * *
266 ******************************************************************************/
str_loc_op(const char * data,const zbx_strloc_t * loc)267 static zbx_prometheus_condition_op_t str_loc_op(const char *data, const zbx_strloc_t *loc)
268 {
269 /* the operation has been already validated during parsing, */
270 /*so there are only three possibilities: */
271 /* '=' - the only sinle character operation */
272 /* '==' - ends with '=' */
273 /* '=~' - ends with '~' */
274
275 if (loc->l == loc->r)
276 return ZBX_PROMETHEUS_CONDITION_OP_EQUAL;
277
278 if ('~' == data[loc->r])
279 return ZBX_PROMETHEUS_CONDITION_OP_REGEX;
280
281 return ZBX_PROMETHEUS_CONDITION_OP_EQUAL_VALUE;
282 }
283
284 /******************************************************************************
285 * *
286 * Function: skip_spaces *
287 * *
288 * Purpose: skips spaces *
289 * *
290 * Parameters: src - [IN] the source string *
291 * pos - [IN] the starting position *
292 * *
293 * Return value: The position of the next non space character. *
294 * *
295 ******************************************************************************/
skip_spaces(const char * data,size_t pos)296 static size_t skip_spaces(const char *data, size_t pos)
297 {
298 while (' ' == data[pos] || '\t' == data[pos])
299 pos++;
300
301 return pos;
302 }
303
304 /******************************************************************************
305 * *
306 * Function: skip_row *
307 * *
308 * Purpose: skips until beginning of the next row *
309 * *
310 * Parameters: src - [IN] the source string *
311 * pos - [IN] the starting position *
312 * *
313 * Return value: The position of the next row space character. *
314 * *
315 ******************************************************************************/
skip_row(const char * data,size_t pos)316 static size_t skip_row(const char *data, size_t pos)
317 {
318 const char *ptr;
319
320 if (NULL == (ptr = strchr(data + pos, '\n')))
321 return strlen(data + pos) + pos;
322
323 return ptr - data + 1;
324 }
325
326 /******************************************************************************
327 * *
328 * Function: parse_metric *
329 * *
330 * Purpose: parses metric name *
331 * *
332 * Parameters: data - [IN] the source string *
333 * pos - [IN] the starting position *
334 * loc - [OUT] the metric location in the source string *
335 * *
336 * Return value: SUCCEED - the metric name was parsed out successfully *
337 * FAIL - otherwise *
338 * *
339 ******************************************************************************/
parse_metric(const char * data,size_t pos,zbx_strloc_t * loc)340 static int parse_metric(const char *data, size_t pos, zbx_strloc_t *loc)
341 {
342 const char *ptr = data + pos;
343
344 if (0 == isalpha(*ptr) && ':' != *ptr && '_' != *ptr)
345 return FAIL;
346
347 while ('\0' != *(++ptr))
348 {
349 if (0 == isalnum(*ptr) && ':' != *ptr && '_' != *ptr)
350 break;
351 }
352
353 loc->l = pos;
354 loc->r = ptr - data - 1;
355
356 return SUCCEED;
357 }
358
359 /******************************************************************************
360 * *
361 * Function: parse_label *
362 * *
363 * Purpose: parses label name *
364 * *
365 * Parameters: data - [IN] the source string *
366 * pos - [IN] the starting position *
367 * loc - [OUT] the label location in the source string *
368 * *
369 * Return value: SUCCEED - the label name was parsed out successfully *
370 * FAIL - otherwise *
371 * *
372 ******************************************************************************/
parse_label(const char * data,size_t pos,zbx_strloc_t * loc)373 static int parse_label(const char *data, size_t pos, zbx_strloc_t *loc)
374 {
375 const char *ptr = data + pos;
376
377 if (0 == isalpha(*ptr) && '_' != *ptr)
378 return FAIL;
379
380 while ('\0' != *(++ptr))
381 {
382 if (0 == isalnum(*ptr) && '_' != *ptr)
383 break;
384 }
385
386 loc->l = pos;
387 loc->r = ptr - data - 1;
388
389 return SUCCEED;
390 }
391
392 /******************************************************************************
393 * *
394 * Function: parse_label_op *
395 * *
396 * Purpose: parses label operation *
397 * *
398 * Parameters: data - [IN] the source string *
399 * pos - [IN] the starting position *
400 * loc - [OUT] the operation location in the source string *
401 * *
402 * Return value: SUCCEED - the label operation was parsed out successfully *
403 * FAIL - otherwise *
404 * *
405 ******************************************************************************/
parse_label_op(const char * data,size_t pos,zbx_strloc_t * loc)406 static int parse_label_op(const char *data, size_t pos, zbx_strloc_t *loc)
407 {
408 const char *ptr = data + pos;
409
410 if ('=' != *ptr)
411 return FAIL;
412
413 loc->l = loc->r = pos;
414
415 if ('~' == ptr[1])
416 loc->r++;
417
418 return SUCCEED;
419 }
420
421 /******************************************************************************
422 * *
423 * Function: parse_label_value *
424 * *
425 * Purpose: parses label value *
426 * *
427 * Parameters: data - [IN] the source string *
428 * pos - [IN] the starting position *
429 * loc - [OUT] the value location in the source string *
430 * *
431 * Return value: SUCCEED - the label value was parsed out successfully *
432 * FAIL - otherwise *
433 * *
434 ******************************************************************************/
parse_label_value(const char * data,size_t pos,zbx_strloc_t * loc)435 static int parse_label_value(const char *data, size_t pos, zbx_strloc_t *loc)
436 {
437 const char *ptr;
438
439 ptr = data + pos;
440
441 if ('"' != *ptr)
442 return FAIL;
443
444 loc->l = pos;
445
446 while ('"' != *(++ptr))
447 {
448 if ('\\' == *ptr)
449 {
450 ptr++;
451
452 if ('\\' != *ptr && 'n' != *ptr && '"' != *ptr)
453 return FAIL;
454 continue;
455 }
456 if ('\0' == *ptr)
457 return FAIL;
458 }
459
460 loc->r = ptr - data;
461
462 return SUCCEED;
463 }
464
465 /******************************************************************************
466 * *
467 * Function: parse_metric_op *
468 * *
469 * Purpose: parses metric operation *
470 * *
471 * Parameters: data - [IN] the source string *
472 * pos - [IN] the starting position *
473 * loc - [OUT] the operation location in the source string *
474 * *
475 * Return value: SUCCEED - the metric operation was parsed out successfully *
476 * FAIL - otherwise *
477 * *
478 ******************************************************************************/
parse_metric_op(const char * data,size_t pos,zbx_strloc_t * loc)479 static int parse_metric_op(const char *data, size_t pos, zbx_strloc_t *loc)
480 {
481 const char *ptr = data + pos;
482
483 if ('=' != *ptr)
484 return FAIL;
485
486 if ('=' != ptr[1])
487 return FAIL;
488
489 loc->l = pos;
490 loc->r = pos + 1;
491
492 return SUCCEED;
493 }
494
495 /******************************************************************************
496 * *
497 * Function: str_copy_lowercase *
498 * *
499 * Purpose: copies lowercase converted string to a buffer *
500 * *
501 * Parameters: dst - [OUT] the output buffer *
502 * size - [IN] the output buffer size *
503 * src - [IN] the source string to copy *
504 * len - [IN] the length of the source string *
505 * *
506 * Return value: The number of bytes copied. *
507 * *
508 ******************************************************************************/
str_copy_lowercase(char * dst,int size,const char * src,int len)509 static int str_copy_lowercase(char *dst, int size, const char *src, int len)
510 {
511 int i;
512
513 if (0 == size)
514 return 0;
515
516 if (size > len + 1)
517 size = len + 1;
518
519 for (i = 0; i < size - 1 && '\0' != *src; i++)
520 *dst++ = tolower(*src++);
521
522 *dst = '\0';
523
524 return i;
525 }
526
527 /******************************************************************************
528 * *
529 * Function: parse_metric_value *
530 * *
531 * Purpose: parses metric value *
532 * *
533 * Parameters: data - [IN] the source string *
534 * pos - [IN] the starting position *
535 * loc - [OUT] the value location in the source string *
536 * *
537 * Return value: SUCCEED - the metric value was parsed out successfully *
538 * FAIL - otherwise *
539 * *
540 ******************************************************************************/
parse_metric_value(const char * data,size_t pos,zbx_strloc_t * loc)541 static int parse_metric_value(const char *data, size_t pos, zbx_strloc_t *loc)
542 {
543 const char *ptr = data + pos;
544 int len;
545 char buffer[4];
546
547 loc->l = pos;
548
549 len = ZBX_CONST_STRLEN("nan");
550 if (len == str_copy_lowercase(buffer, sizeof(buffer), ptr, len) && 0 == memcmp(buffer, "nan", len))
551 {
552 loc->r = pos + 2;
553 return SUCCEED;
554 }
555
556 if ('-' == *ptr || '+' == *ptr)
557 ptr++;
558
559 len = ZBX_CONST_STRLEN("inf");
560 if (len == str_copy_lowercase(buffer, sizeof(buffer), ptr, len) && 0 == memcmp(buffer, "inf", len))
561 {
562 loc->r = ptr - data + 2;
563 return SUCCEED;
564 }
565
566 if (FAIL == zbx_number_parse(ptr, &len))
567 return FAIL;
568
569 loc->r = ptr + len - data - 1;
570
571 return SUCCEED;
572 }
573
574 /******************************************************************************
575 * *
576 * Function: prometheus_condition_free *
577 * *
578 ******************************************************************************/
prometheus_condition_free(zbx_prometheus_condition_t * condition)579 static void prometheus_condition_free(zbx_prometheus_condition_t *condition)
580 {
581 zbx_free(condition->key);
582 zbx_free(condition->pattern);
583 zbx_free(condition);
584 }
585
586 /******************************************************************************
587 * *
588 * Function: prometheus_condition_create *
589 * *
590 * Purpose: allocates and initializes conditionect *
591 * *
592 * Parameters: key - [IN] the key to match *
593 * pattern - [IN] the matching pattern *
594 * op - [IN] the matching operation *
595 * *
596 * Return value: the created condition object *
597 * *
598 ******************************************************************************/
prometheus_condition_create(char * key,char * pattern,zbx_prometheus_condition_op_t op)599 static zbx_prometheus_condition_t *prometheus_condition_create(char *key, char *pattern,
600 zbx_prometheus_condition_op_t op)
601 {
602 zbx_prometheus_condition_t *condition;
603
604 condition = (zbx_prometheus_condition_t *)zbx_malloc(NULL, sizeof(zbx_prometheus_condition_t));
605 condition->key = key;
606 condition->pattern = pattern;
607 condition->op = op;
608
609 return condition;
610 }
611
612 /******************************************************************************
613 * *
614 * Function: prometheus_filter_clear *
615 * *
616 * Purpose: clears resources allocated by prometheus filter *
617 * *
618 * Parameters: filter - [IN] the filter to clear *
619 * *
620 ******************************************************************************/
prometheus_filter_clear(zbx_prometheus_filter_t * filter)621 static void prometheus_filter_clear(zbx_prometheus_filter_t *filter)
622 {
623 if (NULL != filter->metric)
624 prometheus_condition_free(filter->metric);
625
626 if (NULL != filter->value)
627 prometheus_condition_free(filter->value);
628
629 zbx_vector_ptr_clear_ext(&filter->labels, (zbx_clean_func_t)prometheus_condition_free);
630 zbx_vector_ptr_destroy(&filter->labels);
631 }
632
633 /******************************************************************************
634 * *
635 * Function: parse_condition *
636 * *
637 * Purpose: parses condition data - key, pattern and operation *
638 * *
639 * Parameters: data - [IN] the filter data *
640 * pos - [IN] the starting position in filter data *
641 * loc_key - [IN] the condition key location *
642 * loc_op - [IN] the condition operation location *
643 * loc_pattern - [IN] the condition pattern location *
644 * *
645 * Return value: SUCCEED - the condition data was parsed successfully *
646 * FAIL - otherwise *
647 * *
648 ******************************************************************************/
parse_condition(const char * data,size_t pos,zbx_strloc_t * loc_key,zbx_strloc_t * loc_op,zbx_strloc_t * loc_pattern)649 static int parse_condition(const char *data, size_t pos, zbx_strloc_t *loc_key, zbx_strloc_t *loc_op,
650 zbx_strloc_t *loc_pattern)
651 {
652 if (SUCCEED != parse_label(data, pos, loc_key))
653 return FAIL;
654
655 pos = skip_spaces(data, loc_key->r + 1);
656
657 if (SUCCEED != parse_label_op(data, pos, loc_op))
658 return FAIL;
659
660 pos = skip_spaces(data, loc_op->r + 1);
661
662 if (SUCCEED != parse_label_value(data, pos, loc_pattern))
663 return FAIL;
664
665 return SUCCEED;
666 }
667
668 /******************************************************************************
669 * *
670 * Function: prometheus_filter_parse_labels *
671 * *
672 * Purpose: parses label conditions *
673 * *
674 * Parameters: filter - [IN/OUT] the filter *
675 * data - [IN] the filter data *
676 * pos - [IN] the starting position in filter data *
677 * loc - [IN] the location of label conditions *
678 * error - [IN] the error message *
679 * *
680 * Return value: SUCCEED - the label conditions were parsed successfully *
681 * FAIL - otherwise *
682 * *
683 ******************************************************************************/
prometheus_filter_parse_labels(zbx_prometheus_filter_t * filter,const char * data,size_t pos,zbx_strloc_t * loc,char ** error)684 static int prometheus_filter_parse_labels(zbx_prometheus_filter_t *filter, const char *data, size_t pos,
685 zbx_strloc_t *loc, char **error)
686 {
687 zbx_strloc_t loc_key, loc_value, loc_op;
688
689 loc->l = pos;
690 pos = skip_spaces(data, pos + 1);
691
692 while ('}' != data[pos])
693 {
694 if (FAIL == parse_condition(data, pos, &loc_key, &loc_op, &loc_value))
695 {
696 *error = zbx_dsprintf(*error, "cannot parse label condition at \"%s\"", data + pos);
697 return FAIL;
698 }
699
700 if (0 == str_loc_cmp(data, &loc_key, "__name__", ZBX_CONST_STRLEN("__name__")))
701 {
702 if (NULL != filter->metric)
703 {
704 *error = zbx_strdup(*error, "duplicate metric condition specified");
705 return FAIL;
706 }
707
708 filter->metric = prometheus_condition_create(NULL,
709 str_loc_unquote_dyn(data, &loc_value), str_loc_op(data, &loc_op));
710 }
711 else
712 {
713 zbx_prometheus_condition_t *condition;
714
715 condition = prometheus_condition_create(str_loc_dup(data, &loc_key),
716 str_loc_unquote_dyn(data, &loc_value), str_loc_op(data, &loc_op));
717 zbx_vector_ptr_append(&filter->labels, condition);
718 }
719
720 pos = skip_spaces(data, loc_value.r + 1);
721
722 if (',' != data[pos])
723 {
724 if ('}' == data[pos])
725 break;
726
727 *error = zbx_strdup(*error, "missing label condition list terminating character \"}\"");
728 return FAIL;
729 }
730
731 pos = skip_spaces(data, pos + 1);
732 }
733
734 loc->r = pos;
735
736 return SUCCEED;
737 }
738
739 /******************************************************************************
740 * *
741 * Function: prometheus_filter_init *
742 * *
743 * Purpose: initializes prometheus pattern filter from the specified data *
744 * *
745 * Parameters: filter - [IN/OUT] the filter *
746 * data - [IN] the filter data *
747 * error - [IN] the error message *
748 * *
749 * Return value: SUCCEED - the filter was initialized successfully *
750 * FAIL - otherwise *
751 * *
752 ******************************************************************************/
prometheus_filter_init(zbx_prometheus_filter_t * filter,const char * data,char ** error)753 static int prometheus_filter_init(zbx_prometheus_filter_t *filter, const char *data, char **error)
754 {
755 int ret = FAIL;
756 size_t pos = 0;
757 zbx_strloc_t loc;
758
759 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
760
761 memset(filter, 0, sizeof(zbx_prometheus_filter_t));
762 zbx_vector_ptr_create(&filter->labels);
763
764 pos = skip_spaces(data, pos);
765
766 if (SUCCEED == parse_metric(data, pos, &loc))
767 {
768 filter->metric = prometheus_condition_create(NULL, str_loc_dup(data, &loc),
769 ZBX_PROMETHEUS_CONDITION_OP_EQUAL);
770
771 pos = skip_spaces(data, loc.r + 1);
772 }
773
774 if ('{' == data[pos])
775 {
776 if (SUCCEED != prometheus_filter_parse_labels(filter, data, pos, &loc, error))
777 goto out;
778
779 pos = loc.r + 1;
780 }
781
782 pos = skip_spaces(data, pos);
783
784 /* parse metric value condition */
785 if ('\0' != data[pos])
786 {
787 zbx_strloc_t loc_op, loc_value;
788
789 if (SUCCEED != parse_metric_op(data, pos, &loc_op))
790 {
791 *error = zbx_dsprintf(*error, "cannot parse metric comparison operator at \"%s\"", data + pos);
792 goto out;
793 }
794
795 pos = skip_spaces(data, loc_op.r + 1);
796
797 if (SUCCEED != parse_metric_value(data, pos, &loc_value))
798 {
799 *error = zbx_dsprintf(*error, "cannot parse metric comparison value at \"%s\"", data + pos);
800 goto out;
801 }
802
803 pos = skip_spaces(data, loc_value.r + 1);
804 if ('\0' != data[pos])
805 {
806 *error = zbx_dsprintf(*error, "unexpected data after metric comparison value at \"%s\"",
807 data + pos);
808 goto out;
809 }
810
811 filter->value = prometheus_condition_create(NULL, str_loc_dup(data, &loc_value),
812 ZBX_PROMETHEUS_CONDITION_OP_EQUAL_VALUE);
813 zbx_strlower(filter->value->pattern);
814 }
815
816 ret = SUCCEED;
817 out:
818 if (FAIL == ret)
819 {
820 prometheus_filter_clear(filter);
821 zabbix_log(LOG_LEVEL_DEBUG, "%s() Prometheus pattern error: %s", __func__, *error);
822 }
823
824 zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
825
826 return ret;
827 }
828
829 /******************************************************************************
830 * *
831 * Function: prometheus_label_free *
832 * *
833 ******************************************************************************/
prometheus_label_free(zbx_prometheus_label_t * label)834 static void prometheus_label_free(zbx_prometheus_label_t *label)
835 {
836 zbx_free(label->name);
837 zbx_free(label->value);
838 zbx_free(label);
839 }
840
841 /******************************************************************************
842 * *
843 * Function: prometheus_row_free *
844 * *
845 ******************************************************************************/
prometheus_row_free(zbx_prometheus_row_t * row)846 static void prometheus_row_free(zbx_prometheus_row_t *row)
847 {
848 zbx_free(row->metric);
849 zbx_free(row->value);
850 zbx_free(row->raw);
851 zbx_vector_ptr_clear_ext(&row->labels, (zbx_clean_func_t)prometheus_label_free);
852 zbx_vector_ptr_destroy(&row->labels);
853 zbx_free(row);
854 }
855
856 /******************************************************************************
857 * *
858 * Function: condition_match_key_value *
859 * *
860 * Purpose: matches key,value against filter condition *
861 * *
862 * Parameters: condition - [IN] the condition *
863 * key - [IN] the key (optional, can be NULL) *
864 * value - [IN] the value *
865 * *
866 * Return value: SUCCEED - the key,value pair matches condition *
867 * FAIL - otherwise *
868 * *
869 ******************************************************************************/
condition_match_key_value(const zbx_prometheus_condition_t * condition,const char * key,const char * value)870 static int condition_match_key_value(const zbx_prometheus_condition_t *condition, const char *key,
871 const char *value)
872 {
873 /* perform key match, succeeds if key is not defined in filter */
874 if (NULL != condition->key && (NULL == key || 0 != strcmp(key, condition->key)))
875 return FAIL;
876
877 /* match value */
878 switch (condition->op)
879 {
880 case ZBX_PROMETHEUS_CONDITION_OP_EQUAL:
881 case ZBX_PROMETHEUS_CONDITION_OP_EQUAL_VALUE:
882 if (0 != strcmp(value, condition->pattern))
883 return FAIL;
884 break;
885 case ZBX_PROMETHEUS_CONDITION_OP_REGEX:
886 if (NULL == zbx_regexp_match(value, condition->pattern, NULL))
887 return FAIL;
888 break;
889 default:
890 return FAIL;
891 }
892
893 return SUCCEED;
894 }
895
896 /******************************************************************************
897 * *
898 * Function: condition_match_metric_value *
899 * *
900 * Purpose: matches metric value against filter condition *
901 * *
902 * Parameters: pattern - [IN] the condition *
903 * value - [IN] the value *
904 * *
905 * Return value: SUCCEED - the 'value' matches 'condition' *
906 * FAIL - otherwise *
907 * *
908 ******************************************************************************/
condition_match_metric_value(const char * pattern,const char * value)909 static int condition_match_metric_value(const char *pattern, const char *value)
910 {
911 double pattern_dbl, value_dbl;
912 char buffer[5];
913
914 if (SUCCEED != is_double(pattern, &pattern_dbl))
915 {
916 if ('+' == *pattern)
917 pattern++;
918
919 if ('+' == *value)
920 value++;
921
922 zbx_strlcpy(buffer, value, sizeof(buffer));
923 zbx_strlower(buffer);
924 return (0 == strcmp(pattern, buffer) ? SUCCEED : FAIL);
925 }
926
927 if (SUCCEED != is_double(value, &value_dbl))
928 return FAIL;
929
930 if (ZBX_DOUBLE_EPSILON <= fabs(pattern_dbl - value_dbl))
931 return FAIL;
932
933 return SUCCEED;
934 }
935
936 /******************************************************************************
937 * *
938 * Function: prometheus_metric_parse_labels *
939 * *
940 * Purpose: parses metric labels *
941 * *
942 * Parameters: data - [IN] the metric data *
943 * pos - [IN] the starting position in metric data *
944 * labels - [OUT] the parsed labels *
945 * loc - [OUT] the location of label block *
946 * error - [OUT] the error message *
947 * *
948 * Return value: SUCCEED - the labels were parsed successfully *
949 * FAIL - otherwise *
950 * *
951 ******************************************************************************/
prometheus_metric_parse_labels(const char * data,size_t pos,zbx_vector_ptr_t * labels,zbx_strloc_t * loc,char ** error)952 static int prometheus_metric_parse_labels(const char *data, size_t pos, zbx_vector_ptr_t *labels,
953 zbx_strloc_t *loc, char **error)
954 {
955 zbx_strloc_t loc_key, loc_value, loc_op;
956 zbx_prometheus_label_t *label;
957
958 pos = skip_spaces(data, pos + 1);
959 loc->l = pos;
960
961 while ('}' != data[pos])
962 {
963 zbx_prometheus_condition_op_t op;
964
965 if (FAIL == parse_condition(data, pos, &loc_key, &loc_op, &loc_value))
966 {
967 *error = zbx_strdup(*error, "cannot parse label");
968 return FAIL;
969 }
970
971 op = str_loc_op(data, &loc_op);
972 if (ZBX_PROMETHEUS_CONDITION_OP_EQUAL != op)
973 {
974 *error = zbx_strdup(*error, "invalid label assignment operator");
975 return FAIL;
976 }
977
978 label = (zbx_prometheus_label_t *)zbx_malloc(NULL, sizeof(zbx_prometheus_label_t));
979 label->name = str_loc_dup(data, &loc_key);
980 label->value = str_loc_unquote_dyn(data, &loc_value);
981 zbx_vector_ptr_append(labels, label);
982
983 pos = skip_spaces(data, loc_value.r + 1);
984
985 if (',' != data[pos])
986 {
987 if ('}' == data[pos])
988 break;
989
990 *error = zbx_strdup(*error, "missing label list terminating character \"}\"");
991 return FAIL;
992 }
993
994 pos = skip_spaces(data, pos + 1);
995 }
996
997 loc->r = pos;
998
999 return SUCCEED;
1000 }
1001
1002 /******************************************************************************
1003 * *
1004 * Function: prometheus_parse_row *
1005 * *
1006 * Purpose: parses metric row *
1007 * *
1008 * Parameters: filter - [IN] the prometheus filter *
1009 * data - [IN] the metric data *
1010 * pos - [IN] the starting position in metric data *
1011 * prow - [OUT] the parsed row (NULL if did not match filter) *
1012 * loc_row - [OUT] the location of row in prometheus data *
1013 * error - [OUT] the error message *
1014 * *
1015 * Return value: SUCCEED - the row was parsed successfully *
1016 * FAIL - otherwise *
1017 * *
1018 * Comments: If there was no parsing errors, but the row does not match filter*
1019 * conditions then success with NULL prow is be returned. *
1020 * *
1021 ******************************************************************************/
prometheus_parse_row(zbx_prometheus_filter_t * filter,const char * data,size_t pos,zbx_prometheus_row_t ** prow,zbx_strloc_t * loc_row,char ** error)1022 static int prometheus_parse_row(zbx_prometheus_filter_t *filter, const char *data, size_t pos,
1023 zbx_prometheus_row_t **prow, zbx_strloc_t *loc_row, char **error)
1024 {
1025 zbx_strloc_t loc;
1026 zbx_prometheus_row_t *row;
1027 int ret = FAIL, match = SUCCEED, i, j;
1028
1029 loc_row->l = pos;
1030
1031 row = (zbx_prometheus_row_t *)zbx_malloc(NULL, sizeof(zbx_prometheus_row_t));
1032 memset(row, 0, sizeof(zbx_prometheus_row_t));
1033 zbx_vector_ptr_create(&row->labels);
1034
1035 /* parse metric and check against the filter */
1036
1037 if (SUCCEED != parse_metric(data, pos, &loc))
1038 {
1039 *error = zbx_strdup(*error, "cannot parse metric name");
1040 goto out;
1041 }
1042
1043 row->metric = str_loc_dup(data, &loc);
1044
1045 if (NULL != filter->metric)
1046 {
1047 if (FAIL == (match = condition_match_key_value(filter->metric, NULL, row->metric)))
1048 goto out;
1049 }
1050
1051 /* parse labels and check against the filter */
1052
1053 pos = skip_spaces(data, loc.r + 1);
1054
1055 if ('{' == data[pos])
1056 {
1057 if (SUCCEED != prometheus_metric_parse_labels(data, pos, &row->labels, &loc, error))
1058 goto out;
1059
1060 for (i = 0; i < filter->labels.values_num; i++)
1061 {
1062 zbx_prometheus_condition_t *condition = filter->labels.values[i];
1063
1064 for (j = 0; j < row->labels.values_num; j++)
1065 {
1066 zbx_prometheus_label_t *label = row->labels.values[j];
1067
1068 if (SUCCEED == condition_match_key_value(condition, label->name, label->value))
1069 break;
1070 }
1071
1072 if (j == row->labels.values_num)
1073 {
1074 /* no matching labels */
1075 match = FAIL;
1076 goto out;
1077 }
1078 }
1079
1080 pos = skip_spaces(data, loc.r + 1);
1081 }
1082 else /* no labels in row */
1083 {
1084 if (0 < filter->labels.values_num) /* got labels in filter */
1085 {
1086 match = FAIL;
1087 goto out;
1088 }
1089 }
1090
1091 /* check if there was a whitespace before metric value */
1092 if (pos == loc.r + 1)
1093 {
1094 const char *ptr;
1095 int len;
1096
1097 if (NULL == (ptr = strchr(data + pos, '\n')))
1098 len = strlen(data + pos);
1099 else
1100 len = ptr - data + pos;
1101
1102 *error = zbx_dsprintf(*error, "cannot parse text at: %.*s", len, data + pos);
1103 goto out;
1104 }
1105
1106 /* parse value and check against the filter */
1107
1108 if (FAIL == parse_metric_value(data, pos, &loc))
1109 {
1110 *error = zbx_strdup(*error, "cannot parse metric value");
1111 goto out;
1112 }
1113 row->value = str_loc_dup(data, &loc);
1114
1115 if (NULL != filter->value)
1116 {
1117 if (SUCCEED != (match = condition_match_metric_value(filter->value->pattern, row->value)))
1118 goto out;
1119 }
1120
1121 pos = loc.r + 1;
1122
1123 if (' ' != data[pos] && '\t' != data[pos] && '\n' != data[pos] && '\0' != data[pos])
1124 {
1125 *error = zbx_dsprintf(*error, "invalid character '%c' following metric value", data[pos]);
1126 goto out;
1127 }
1128
1129 /* row was successfully parsed and matched all filter conditions */
1130 ret = SUCCEED;
1131 out:
1132 if (FAIL == ret)
1133 {
1134 prometheus_row_free(row);
1135 *prow = NULL;
1136
1137 /* match failure, return success with NULL row */
1138 if (FAIL == match)
1139 ret = SUCCEED;
1140 }
1141 else
1142 *prow = row;
1143
1144 if (SUCCEED == ret)
1145 {
1146 /* find the row location */
1147
1148 pos = skip_row(data, pos);
1149 if ('\n' == data[--pos])
1150 pos--;
1151
1152 loc_row->r = pos;
1153 }
1154
1155 return ret;
1156 }
1157
1158 /******************************************************************************
1159 * *
1160 * Function: parse_help *
1161 * *
1162 * Purpose: parses HELP comment metric and help text *
1163 * *
1164 * Parameters: data - [IN] the prometheus data *
1165 * pos - [IN] the starting position in metric data *
1166 * loc_metric - [OUT] the metric location in data *
1167 * loc_help - [OUT] the help location in data *
1168 * *
1169 * Return value: SUCCEED - the help hint was parsed successfully *
1170 * FAIL - otherwise *
1171 * *
1172 ******************************************************************************/
parse_help(const char * data,size_t pos,zbx_strloc_t * loc_metric,zbx_strloc_t * loc_help)1173 static int parse_help(const char *data, size_t pos, zbx_strloc_t *loc_metric, zbx_strloc_t *loc_help)
1174 {
1175 const char *ptr;
1176
1177 if (SUCCEED != parse_metric(data, pos, loc_metric))
1178 return FAIL;
1179
1180 pos = skip_spaces(data, loc_metric->r + 1);
1181 loc_help->l = pos;
1182
1183 for (ptr = data + pos; '\0' != *ptr && '\n' != *ptr;)
1184 {
1185 if ('\\' == *ptr++)
1186 {
1187 if ('\\' != *ptr && 'n' != *ptr)
1188 return FAIL;
1189 ptr++;
1190 }
1191 }
1192
1193 loc_help->r = ptr - data - 1;
1194
1195 return SUCCEED;
1196 }
1197
1198 /******************************************************************************
1199 * *
1200 * Function: parse_type *
1201 * *
1202 * Purpose: parses TYPE comment metric and the type *
1203 * *
1204 * Parameters: data - [IN] the prometheus data *
1205 * pos - [IN] the starting position in metric data *
1206 * loc_metric - [OUT] the metric location in data *
1207 * loc_type - [OUT] the type location in data *
1208 * *
1209 * Return value: SUCCEED - the type hint was parsed successfully *
1210 * FAIL - otherwise *
1211 * *
1212 ******************************************************************************/
parse_type(const char * data,size_t pos,zbx_strloc_t * loc_metric,zbx_strloc_t * loc_type)1213 static int parse_type(const char *data, size_t pos, zbx_strloc_t *loc_metric, zbx_strloc_t *loc_type)
1214 {
1215 const char *ptr;
1216
1217 if (SUCCEED != parse_metric(data, pos, loc_metric))
1218 return FAIL;
1219
1220 pos = skip_spaces(data, loc_metric->r + 1);
1221 loc_type->l = pos;
1222 ptr = data + pos;
1223 while (0 != isalpha(*ptr))
1224 ptr++;
1225
1226 /* invalid metric type */
1227 if (pos == (loc_type->r = ptr - data))
1228 return FAIL;
1229
1230 loc_type->r--;
1231
1232 return SUCCEED;
1233 }
1234
1235 /******************************************************************************
1236 * *
1237 * Function: prometheus_register_hint *
1238 * *
1239 * Purpose: registers TYPE/HELP comment hint to the specified metric *
1240 * *
1241 * Parameters: hints - [IN/OUT] the hint registry *
1242 * data - [IN] the prometheus data *
1243 * metric - [IN] the metric *
1244 * loc_hint - [IN] the hint location in prometheus data *
1245 * hint_type - [IN] the hint type *
1246 * error - [OUT] the error message *
1247 * *
1248 * Return value: SUCCEED - the hint was registered successfully *
1249 * FAIL - otherwise *
1250 * *
1251 ******************************************************************************/
prometheus_register_hint(zbx_hashset_t * hints,const char * data,char * metric,const zbx_strloc_t * loc_hint,int hint_type,char ** error)1252 static int prometheus_register_hint(zbx_hashset_t *hints, const char *data, char *metric,
1253 const zbx_strloc_t *loc_hint, int hint_type, char **error)
1254 {
1255 zbx_prometheus_hint_t *hint, hint_local;
1256 zbx_strloc_t loc = *loc_hint;
1257
1258 hint_local.metric = metric;
1259
1260 if (NULL == (hint = (zbx_prometheus_hint_t *)zbx_hashset_search(hints, &hint_local)))
1261 {
1262 hint = zbx_hashset_insert(hints, &hint_local, sizeof(hint_local));
1263 hint->type = NULL;
1264 hint->help = NULL;
1265 }
1266 else
1267 zbx_free(metric);
1268
1269 while ((' ' == data[loc.r] || '\t' == data[loc.r]) && loc.r > loc.l)
1270 loc.r--;
1271
1272 if (ZBX_PROMETHEUS_HINT_HELP == hint_type)
1273 {
1274 if (NULL != hint->help)
1275 {
1276 *error = zbx_dsprintf(*error, "multiple HELP comments found for metric \"%s\"", hint->metric);
1277 return FAIL;
1278 }
1279 hint->help = str_loc_unescape_hint_dyn(data, &loc);
1280 }
1281 else /* ZBX_PROMETHEUS_HINT_TYPE */
1282 {
1283 if (NULL != hint->type)
1284 {
1285 *error = zbx_dsprintf(*error, "multiple TYPE comments found for metric \"%s\"", hint->metric);
1286 return FAIL;
1287 }
1288 hint->type = str_loc_dup(data, &loc);
1289 }
1290
1291 return SUCCEED;
1292 }
1293
1294 /******************************************************************************
1295 * *
1296 * Function: prometheus_parse_hint *
1297 * *
1298 * Purpose: parses TYPE/HELP comment hint and registers it *
1299 * *
1300 * Parameters: filter - [IN] the prometheus filter *
1301 * data - [IN] the prometheus data *
1302 * pso - [IN] the position of comments in prometheus data *
1303 * hints - [IN/OUT] the hint registry *
1304 * error - [OUT] the error message *
1305 * *
1306 * Return value: SUCCEED - the hint was registered successfully *
1307 * FAIL - otherwise *
1308 * *
1309 ******************************************************************************/
prometheus_parse_hint(zbx_prometheus_filter_t * filter,const char * data,size_t pos,zbx_hashset_t * hints,zbx_strloc_t * loc,char ** error)1310 static int prometheus_parse_hint(zbx_prometheus_filter_t *filter, const char *data, size_t pos,
1311 zbx_hashset_t *hints, zbx_strloc_t *loc, char **error)
1312 {
1313 int ret, hint_type;
1314 zbx_strloc_t loc_metric, loc_hint;
1315 char *metric;
1316
1317 loc->l = pos;
1318 pos = skip_spaces(data, pos + 1);
1319
1320 if ('\0' == data[pos])
1321 {
1322 loc->r = pos - 1;
1323 return SUCCEED;
1324 }
1325
1326 if (0 == strncmp(data + pos, "HELP", 4))
1327 {
1328 pos = skip_spaces(data, pos + 4);
1329 ret = parse_help(data, pos, &loc_metric, &loc_hint);
1330 hint_type = ZBX_PROMETHEUS_HINT_HELP;
1331 }
1332 else if (0 == strncmp(data + pos, "TYPE", 4))
1333 {
1334 pos = skip_spaces(data, pos + 4);
1335 ret = parse_type(data, pos, &loc_metric, &loc_hint);
1336 hint_type = ZBX_PROMETHEUS_HINT_TYPE;
1337 }
1338 else
1339 {
1340 /* skip the comment */
1341 const char *ptr;
1342
1343 if (NULL != (ptr = strchr(data + pos, '\n')))
1344 loc->r = ptr - data - 1;
1345 else
1346 loc->r = strlen(data + pos) + pos - 1;
1347
1348 return SUCCEED;
1349 }
1350
1351 if (SUCCEED != ret)
1352 {
1353 *error = zbx_strdup(*error, "cannot parse comment");
1354 return FAIL;
1355 }
1356
1357 loc->r = loc_hint.r;
1358 metric = str_loc_dup(data, &loc_metric);
1359
1360 /* skip hints of metrics not matching filter */
1361 if (NULL != filter->metric && SUCCEED != condition_match_key_value(filter->metric, NULL, metric))
1362 {
1363 zbx_free(metric);
1364 return SUCCEED;
1365 }
1366
1367 return prometheus_register_hint(hints, data, metric, &loc_hint, hint_type, error);
1368 }
1369
1370 /******************************************************************************
1371 * *
1372 * Function: prometheus_parse_rows *
1373 * *
1374 * Purpose: parses rows with metrics from prometheus data *
1375 * *
1376 * Parameters: filter - [IN] the prometheus filter *
1377 * data - [IN] the metric data *
1378 * rows - [OUT] the parsed rows *
1379 * hints - [OUT] the TYPE/HELP hint registry (optional) *
1380 * error - [OUT] the error message *
1381 * *
1382 * Return value: SUCCEED - the rows were parsed successfully *
1383 * FAIL - otherwise *
1384 * *
1385 ******************************************************************************/
prometheus_parse_rows(zbx_prometheus_filter_t * filter,const char * data,zbx_vector_ptr_t * rows,zbx_hashset_t * hints,char ** error)1386 static int prometheus_parse_rows(zbx_prometheus_filter_t *filter, const char *data, zbx_vector_ptr_t *rows,
1387 zbx_hashset_t *hints, char **error)
1388 {
1389 size_t pos = 0;
1390 int row_num = 1, ret = FAIL;
1391 zbx_prometheus_row_t *row;
1392 char *errmsg = NULL;
1393 zbx_strloc_t loc;
1394
1395 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
1396
1397 for (pos = 0; '\0' != data[pos]; pos = skip_row(data, pos), row_num++)
1398 {
1399 pos = skip_spaces(data, pos);
1400
1401 /* skip empty strings */
1402 if ('\n' == data[pos])
1403 continue;
1404
1405 if ('#' == data[pos])
1406 {
1407 if (NULL != hints)
1408 {
1409 if (SUCCEED != prometheus_parse_hint(filter, data, pos, hints, &loc, &errmsg))
1410 goto out;
1411 pos = loc.r + 1;
1412 }
1413 continue;
1414 }
1415
1416 if (SUCCEED != prometheus_parse_row(filter, data, pos, &row, &loc, &errmsg))
1417 goto out;
1418
1419 if (NULL != row)
1420 {
1421 row->raw = str_loc_dup(data, &loc);
1422 zbx_vector_ptr_append(rows, row);
1423 }
1424
1425 pos = loc.r + 1;
1426 }
1427
1428 ret = SUCCEED;
1429 out:
1430 if (SUCCEED != ret)
1431 {
1432 const char *ptr, *suffix = "";
1433 int len;
1434
1435 if (NULL != (ptr = strchr(data + pos, '\n')))
1436 len = ptr - data - pos;
1437 else
1438 len = strlen(data + pos);
1439
1440 if (ZBX_PROMEHTEUS_ERROR_MAX_ROW_LENGTH < len)
1441 {
1442 len = ZBX_PROMEHTEUS_ERROR_MAX_ROW_LENGTH;
1443 suffix = "...";
1444 }
1445 *error = zbx_dsprintf(*error, "data parsing error at row %d \"%.*s%s\": %s", row_num, len, data + pos,
1446 suffix, errmsg);
1447 zbx_free(errmsg);
1448 }
1449
1450 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s rows:%d hints:%d", __func__, zbx_result_string(ret),
1451 rows->values_num, (NULL == hints ? 0 : hints->num_data));
1452 return ret;
1453 }
1454
1455 /******************************************************************************
1456 * *
1457 * Function: prometheus_extract_value *
1458 * *
1459 * Purpose: extracts value from filtered rows according to output template *
1460 * *
1461 * Parameters: filter - [IN] the prometheus filter *
1462 * output - [IN] the output template *
1463 * value - [OUT] the extracted value *
1464 * error - [OUT] the error message *
1465 * *
1466 * Return value: SUCCEED - the value was extracted successfully *
1467 * FAIL - otherwise *
1468 * *
1469 ******************************************************************************/
prometheus_extract_value(zbx_vector_ptr_t * rows,const char * output,char ** value,char ** error)1470 static int prometheus_extract_value(zbx_vector_ptr_t *rows, const char *output, char **value, char **error)
1471 {
1472 zbx_prometheus_row_t *row;
1473
1474 if (0 == rows->values_num)
1475 {
1476 *error = zbx_strdup(*error, "no matching metrics found");
1477 return FAIL;
1478 }
1479
1480 if (1 < rows->values_num)
1481 {
1482 int i, rows_num = ZBX_PROMETHEUS_ERROR_ROW_NUM;
1483 size_t error_alloc, error_offset = 0;
1484
1485 error_alloc = (NULL == *error ? 0 : strlen(*error) + 1);
1486
1487 zbx_strcpy_alloc(error, &error_alloc, &error_offset, "multiple matching metrics found:\n\n");
1488
1489 if (rows->values_num < rows_num)
1490 rows_num = rows->values_num;
1491
1492 for (i = 0; i < rows_num; i++)
1493 {
1494 row = (zbx_prometheus_row_t *)rows->values[i];
1495 zbx_strcpy_alloc(error, &error_alloc, &error_offset, row->raw);
1496 zbx_chrcpy_alloc(error, &error_alloc, &error_offset, '\n');
1497 }
1498
1499 if (rows->values_num > rows_num)
1500 zbx_strcpy_alloc(error, &error_alloc, &error_offset, "...");
1501 else
1502 (*error)[error_offset - 1] = '\0';
1503
1504 return FAIL;
1505 }
1506
1507 row = (zbx_prometheus_row_t *)rows->values[0];
1508
1509 if ('\0' != *output)
1510 {
1511 int i;
1512
1513 for (i = 0; i < row->labels.values_num; i++)
1514 {
1515 const zbx_prometheus_label_t *label = (const zbx_prometheus_label_t *)row->labels.values[i];
1516
1517 if (0 == strcmp(label->name, output))
1518 {
1519 *value = zbx_strdup(NULL, label->value);
1520 break;
1521 }
1522 }
1523
1524 if (i == row->labels.values_num)
1525 {
1526 *error = zbx_strdup(*error, "no label matches the specified output");
1527 return FAIL;
1528 }
1529 }
1530 else
1531 *value = zbx_strdup(NULL, row->value);
1532
1533 return SUCCEED;
1534 }
1535
1536 /******************************************************************************
1537 * *
1538 * Function: zbx_prometheus_pattern *
1539 * *
1540 * Purpose: extracts value from prometheus data by the specified filter *
1541 * *
1542 * Parameters: data - [IN] the prometheus data *
1543 * fitler_data - [IN] the filter in text format *
1544 * output - [IN] the output template *
1545 * value - [OUT] the extracted value *
1546 * error - [OUT] the error message *
1547 * *
1548 * Return value: SUCCEED - the value was extracted successfully *
1549 * FAIL - otherwise *
1550 * *
1551 ******************************************************************************/
zbx_prometheus_pattern(const char * data,const char * filter_data,const char * output,char ** value,char ** error)1552 int zbx_prometheus_pattern(const char *data, const char *filter_data, const char *output, char **value,
1553 char **error)
1554 {
1555 zbx_prometheus_filter_t filter;
1556 char *errmsg = NULL;
1557 int ret = FAIL;
1558 zbx_vector_ptr_t rows;
1559
1560 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
1561
1562 if (FAIL == prometheus_filter_init(&filter, filter_data, &errmsg))
1563 {
1564 *error = zbx_dsprintf(*error, "pattern error: %s", errmsg);
1565 zbx_free(errmsg);
1566 goto out;
1567 }
1568
1569 zbx_vector_ptr_create(&rows);
1570
1571 if (FAIL == prometheus_parse_rows(&filter, data, &rows, NULL, error))
1572 goto cleanup;
1573
1574 if (FAIL == prometheus_extract_value(&rows, output, value, &errmsg))
1575 {
1576 *error = zbx_dsprintf(*error, "data extraction error: %s", errmsg);
1577 zbx_free(errmsg);
1578 goto cleanup;
1579 }
1580
1581 zabbix_log(LOG_LEVEL_DEBUG, "%s(): output:%s", __func__, *value);
1582 ret = SUCCEED;
1583 cleanup:
1584 zbx_vector_ptr_clear_ext(&rows, (zbx_clean_func_t)prometheus_row_free);
1585 zbx_vector_ptr_destroy(&rows);
1586 prometheus_filter_clear(&filter);
1587 out:
1588 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
1589 return ret;
1590 }
1591
1592 /******************************************************************************
1593 * *
1594 * Function: zbx_prometheus_to_json *
1595 * *
1596 * Purpose: converts filtered prometheus data to json to be used with LLD *
1597 * *
1598 * Parameters: data - [IN] the prometheus data *
1599 * fitler_data - [IN] the filter in text format *
1600 * value - [OUT] the converted data *
1601 * error - [OUT] the error message *
1602 * *
1603 * Return value: SUCCEED - the data was converted successfully *
1604 * FAIL - otherwise *
1605 * *
1606 ******************************************************************************/
zbx_prometheus_to_json(const char * data,const char * filter_data,char ** value,char ** error)1607 int zbx_prometheus_to_json(const char *data, const char *filter_data, char **value, char **error)
1608 {
1609 zbx_prometheus_filter_t filter;
1610 char *errmsg = NULL;
1611 int ret = FAIL, i, j;
1612 zbx_vector_ptr_t rows;
1613 zbx_hashset_t hints;
1614 zbx_prometheus_hint_t *hint, hint_local;
1615 zbx_hashset_iter_t iter;
1616 struct zbx_json json;
1617
1618 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
1619
1620 if (FAIL == prometheus_filter_init(&filter, filter_data, &errmsg))
1621 {
1622 *error = zbx_dsprintf(*error, "pattern error: %s", errmsg);
1623 zbx_free(errmsg);
1624 goto out;
1625 }
1626
1627 zbx_vector_ptr_create(&rows);
1628 zbx_hashset_create(&hints, 100, prometheus_hint_hash, prometheus_hint_compare);
1629
1630 if (FAIL == prometheus_parse_rows(&filter, data, &rows, &hints, error))
1631 goto cleanup;
1632
1633 zbx_json_initarray(&json, rows.values_num * 100);
1634
1635 for (i = 0; i < rows.values_num; i++)
1636 {
1637 zbx_prometheus_row_t *row = (zbx_prometheus_row_t *)rows.values[i];
1638 char *hint_type;
1639
1640 zbx_json_addobject(&json, NULL);
1641 zbx_json_addstring(&json, ZBX_PROTO_TAG_NAME, row->metric, ZBX_JSON_TYPE_STRING);
1642 zbx_json_addstring(&json, ZBX_PROTO_TAG_VALUE, row->value, ZBX_JSON_TYPE_STRING);
1643 zbx_json_addstring(&json, ZBX_PROTO_TAG_LINE_RAW, row->raw, ZBX_JSON_TYPE_STRING);
1644
1645 if (0 != row->labels.values_num)
1646 {
1647 zbx_json_addobject(&json, ZBX_PROTO_TAG_LABELS);
1648
1649 for (j = 0; j < row->labels.values_num; j++)
1650 {
1651 zbx_prometheus_label_t *label = (zbx_prometheus_label_t *)row->labels.values[j];
1652 zbx_json_addstring(&json, label->name, label->value, ZBX_JSON_TYPE_STRING);
1653 }
1654
1655 zbx_json_close(&json);
1656 }
1657
1658 hint_local.metric = row->metric;
1659 hint = (zbx_prometheus_hint_t *)zbx_hashset_search(&hints, &hint_local);
1660
1661 hint_type = (NULL != hint && NULL != hint->type ? hint->type : ZBX_PROMETHEUS_TYPE_UNTYPED);
1662 zbx_json_addstring(&json, ZBX_PROTO_TAG_TYPE, hint_type, ZBX_JSON_TYPE_STRING);
1663
1664 if (NULL != hint && NULL != hint->help)
1665 zbx_json_addstring(&json, ZBX_PROTO_TAG_HELP, hint->help, ZBX_JSON_TYPE_STRING);
1666
1667 zbx_json_close(&json);
1668 }
1669
1670 *value = zbx_strdup(NULL, json.buffer);
1671 zbx_json_free(&json);
1672 zabbix_log(LOG_LEVEL_DEBUG, "%s(): output:%s", __func__, *value);
1673 ret = SUCCEED;
1674 cleanup:
1675 zbx_hashset_iter_reset(&hints, &iter);
1676 while (NULL != (hint = (zbx_prometheus_hint_t *)zbx_hashset_iter_next(&iter)))
1677 {
1678 zbx_free(hint->metric);
1679 zbx_free(hint->help);
1680 zbx_free(hint->type);
1681 }
1682 zbx_hashset_destroy(&hints);
1683
1684 zbx_vector_ptr_clear_ext(&rows, (zbx_clean_func_t)prometheus_row_free);
1685 zbx_vector_ptr_destroy(&rows);
1686 prometheus_filter_clear(&filter);
1687 out:
1688 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
1689 return ret;
1690 }
1691
zbx_prometheus_validate_filter(const char * pattern,char ** error)1692 int zbx_prometheus_validate_filter(const char *pattern, char **error)
1693 {
1694 zbx_prometheus_filter_t filter;
1695
1696 if (FAIL == prometheus_filter_init(&filter, pattern, error))
1697 return FAIL;
1698
1699 prometheus_filter_clear(&filter);
1700 return SUCCEED;
1701 }
1702
zbx_prometheus_validate_label(const char * label)1703 int zbx_prometheus_validate_label(const char *label)
1704 {
1705 zbx_strloc_t loc;
1706 size_t pos;
1707
1708 if ('\0' == *label)
1709 return SUCCEED;
1710
1711 if (SUCCEED != parse_label(label, 0, &loc))
1712 return FAIL;
1713
1714 pos = skip_spaces(label, loc.r + 1);
1715 if ('\0' != label[pos])
1716 return FAIL;
1717
1718 return SUCCEED;
1719 }
1720
1721
1722 #ifdef HAVE_TESTS
1723 # include "../../../tests/libs/zbxprometheus/prometheus_test.c"
1724 #endif
1725