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