/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ #include "zbxprometheus.h" #include "log.h" #include "zbxalgo.h" #include "zbxeval.h" #include "zbxexpr.h" #include "zbxjson.h" #include "zbxnum.h" #include "zbxregexp.h" #include "zbxstr.h" #include "zbxtypes.h" #define ZBX_PROMETHEUS_HINT_HELP 0 #define ZBX_PROMETHEUS_HINT_TYPE 1 typedef enum { ZBX_PROMETHEUS_CONDITION_OP_EQUAL, ZBX_PROMETHEUS_CONDITION_OP_REGEX, ZBX_PROMETHEUS_CONDITION_OP_EQUAL_VALUE, ZBX_PROMETHEUS_CONDITION_OP_NOT_EQUAL, ZBX_PROMETHEUS_CONDITION_OP_REGEX_NOT_MATCHED } zbx_prometheus_condition_op_t; /* key-value matching data */ typedef struct { /* the key to match, optional - can be NULL */ char *key; /* the pattern to match */ char *pattern; /* the condition operations */ zbx_prometheus_condition_op_t op; } zbx_prometheus_condition_t; ZBX_PTR_VECTOR_DECL(prometheus_condition, zbx_prometheus_condition_t *) /* the prometheus pattern filter */ typedef struct { /* metric filter, optional - can be NULL */ zbx_prometheus_condition_t *metric; /* value filter, optional - can be NULL */ zbx_prometheus_condition_t *value; /* label filters */ zbx_vector_prometheus_condition_t labels; } zbx_prometheus_filter_t; /* the prometheus metric HELP, TYPE hints in comments */ typedef struct { char *metric; char *type; char *help; } zbx_prometheus_hint_t; /* indexing support */ typedef struct { char *value; zbx_vector_prometheus_row_t rows; } zbx_prometheus_index_t; /* TYPE, HELP hint hashset support */ static zbx_hash_t prometheus_hint_hash(const void *d) { const zbx_prometheus_hint_t *hint = (const zbx_prometheus_hint_t *)d; return ZBX_DEFAULT_STRING_HASH_FUNC(hint->metric); } static int prometheus_hint_compare(const void *d1, const void *d2) { const zbx_prometheus_hint_t *hint1 = (const zbx_prometheus_hint_t *)d1; const zbx_prometheus_hint_t *hint2 = (const zbx_prometheus_hint_t *)d2; return strcmp(hint1->metric, hint2->metric); } ZBX_PTR_VECTOR_IMPL(prometheus_label, zbx_prometheus_label_t *) ZBX_PTR_VECTOR_IMPL(prometheus_row, zbx_prometheus_row_t *) ZBX_PTR_VECTOR_IMPL(prometheus_label_index, zbx_prometheus_label_index_t *) ZBX_PTR_VECTOR_IMPL(prometheus_condition, zbx_prometheus_condition_t *) /****************************************************************************** * * * Purpose: allocates and copies substring at the specified location * * * * Parameters: src - [IN] the source string * * loc - [IN] the substring location * * * * Return value: The copied substring. * * * ******************************************************************************/ static char *str_loc_dup(const char *src, const zbx_strloc_t *loc) { char *str; size_t len; len = loc->r - loc->l + 1; str = zbx_malloc(NULL, len + 1); memcpy(str, src + loc->l, len); str[len] = '\0'; return str; } /****************************************************************************** * * * Purpose: unquotes substring at the specified location * * * * Parameters: src - [IN] the source string * * loc - [IN] the substring location * * * * Return value: The unquoted and copied substring. * * * ******************************************************************************/ static char *str_loc_unquote_dyn(const char *src, const zbx_strloc_t *loc) { char *str, *ptr; src += loc->l + 1; str = ptr = zbx_malloc(NULL, loc->r - loc->l); while ('"' != *src) { if ('\\' == *src) { switch (*(++src)) { case '\\': *ptr++ = '\\'; break; case 'n': *ptr++ = '\n'; break; case '"': *ptr++ = '"'; break; } } else *ptr++ = *src; src++; } *ptr = '\0'; return str; } /****************************************************************************** * * * Purpose: unescapes HELP hint * * * * Parameters: src - [IN] the source string * * loc - [IN] the substring location * * * * Return value: The unescaped and copied HELP string. * * * ******************************************************************************/ static char *str_loc_unescape_hint_dyn(const char *src, const zbx_strloc_t *loc) { char *str, *pout; const char *pin; size_t len; len = loc->r - loc->l + 1; str = zbx_malloc(NULL, len + 1); for (pout = str, pin = src + loc->l; pin <= src + loc->r; pin++) { if ('\\' == *pin) { pin++; switch (*pin) { case '\\': *pout++ = '\\'; break; case 'n': *pout++ = '\n'; break; default: THIS_SHOULD_NEVER_HAPPEN; *pout++ = '?'; } } else *pout++ = *pin; } *pout++ ='\0'; return str; } /****************************************************************************** * * * Purpose: compares substring at the specified location with the specified * * text * * * * Parameters: src - [IN] the source string * * loc - [IN] the substring location * * text - [IN] the text to compare with * * text_len - [IN] the text length * * * * Return value: -1 - the substring is less than the specified text * * 0 - the substring is equal to the specified text * * 1 - the substring is greater than the specified text * * * ******************************************************************************/ static int str_loc_cmp(const char *src, const zbx_strloc_t *loc, const char *text, size_t text_len) { ZBX_RETURN_IF_NOT_EQUAL(loc->r - loc->l + 1, text_len); return memcmp(src + loc->l, text, text_len); } /****************************************************************************** * * * Purpose: parses condition operation at the specified location * * * * Parameters: src - [IN] the source string * * loc - [IN] the substring location * * * * Return value: The condition operation. * * * ******************************************************************************/ static zbx_prometheus_condition_op_t str_loc_op(const char *src, const zbx_strloc_t *loc) { if ('=' == src[loc->l]) { if ('~' == src[loc->r]) return ZBX_PROMETHEUS_CONDITION_OP_REGEX; else return ZBX_PROMETHEUS_CONDITION_OP_EQUAL; } else if ('!' == src[loc->l]) { if ('~' == src[loc->r]) return ZBX_PROMETHEUS_CONDITION_OP_REGEX_NOT_MATCHED; else return ZBX_PROMETHEUS_CONDITION_OP_NOT_EQUAL; } return ZBX_PROMETHEUS_CONDITION_OP_EQUAL_VALUE; } /****************************************************************************** * * * Purpose: skips spaces * * * * Parameters: data - [IN] the source string * * pos - [IN] the starting position * * * * Return value: The position of the next non space character. * * * ******************************************************************************/ static size_t skip_spaces(const char *data, size_t pos) { while (' ' == data[pos] || '\t' == data[pos]) pos++; return pos; } /****************************************************************************** * * * Purpose: skips until beginning of the next row * * * * Parameters: src - [IN] the source string * * pos - [IN] the starting position * * * * Return value: The position of the next row space character. * * * ******************************************************************************/ static size_t skip_row(const char *src, size_t pos) { const char *ptr; if (NULL == (ptr = strchr(src + pos, '\n'))) return strlen(src + pos) + pos; return (size_t)(ptr - src + 1); } /****************************************************************************** * * * Purpose: parses metric name * * * * Parameters: data - [IN] the source string * * pos - [IN] the starting position * * loc - [OUT] the metric location in the source string * * * * Return value: SUCCEED - the metric name was parsed out successfully * * FAIL - otherwise * * * ******************************************************************************/ static int parse_metric(const char *data, size_t pos, zbx_strloc_t *loc) { const char *ptr = data + pos; if (0 == isalpha(*ptr) && ':' != *ptr && '_' != *ptr) return FAIL; while ('\0' != *(++ptr)) { if (0 == isalnum(*ptr) && ':' != *ptr && '_' != *ptr) break; } loc->l = pos; loc->r = (size_t)(ptr - data) - 1; return SUCCEED; } /****************************************************************************** * * * Purpose: parses label name * * * * Parameters: data - [IN] the source string * * pos - [IN] the starting position * * loc - [OUT] the label location in the source string * * * * Return value: SUCCEED - the label name was parsed out successfully * * FAIL - otherwise * * * ******************************************************************************/ static int parse_label(const char *data, size_t pos, zbx_strloc_t *loc) { const char *ptr = data + pos; if (0 == isalpha(*ptr) && '_' != *ptr) return FAIL; while ('\0' != *(++ptr)) { if (0 == isalnum(*ptr) && '_' != *ptr) break; } loc->l = pos; loc->r = (size_t)(ptr - data) - 1; return SUCCEED; } /****************************************************************************** * * * Purpose: parses label operation * * * * Parameters: data - [IN] the source string * * pos - [IN] the starting position * * loc - [OUT] the operation location in the source string * * * * Return value: SUCCEED - the label operation was parsed out successfully * * FAIL - otherwise * * * ******************************************************************************/ static int parse_label_op(const char *data, size_t pos, zbx_strloc_t *loc) { if ('=' == data[pos]) { loc->l = pos; if ('~' == data[pos + 1]) loc->r = pos + 1; /* =~ */ else loc->r = pos; /* = */ return SUCCEED; } else if ('!' == data[pos] && ('=' == data[pos + 1] || '~' == data[pos + 1])) { /* != or !~ */ loc->l = pos; loc->r = pos + 1; return SUCCEED; } return FAIL; } /****************************************************************************** * * * Purpose: parses label value * * * * Parameters: data - [IN] the source string * * pos - [IN] the starting position * * loc - [OUT] the value location in the source string * * * * Return value: SUCCEED - the label value was parsed out successfully * * FAIL - otherwise * * * ******************************************************************************/ static int parse_label_value(const char *data, size_t pos, zbx_strloc_t *loc) { const char *ptr; ptr = data + pos; if ('"' != *ptr) return FAIL; loc->l = pos; while ('"' != *(++ptr)) { if ('\\' == *ptr) { ptr++; if ('\\' != *ptr && 'n' != *ptr && '"' != *ptr) return FAIL; continue; } if ('\0' == *ptr) return FAIL; } loc->r = (size_t)(ptr - data); return SUCCEED; } /****************************************************************************** * * * Purpose: parses metric operation * * * * Parameters: data - [IN] the source string * * pos - [IN] the starting position * * loc - [OUT] the operation location in the source string * * * * Return value: SUCCEED - the metric operation was parsed out successfully * * FAIL - otherwise * * * ******************************************************************************/ static int parse_metric_op(const char *data, size_t pos, zbx_strloc_t *loc) { const char *ptr = data + pos; if ('=' != *ptr) return FAIL; if ('=' != ptr[1]) return FAIL; loc->l = pos; loc->r = pos + 1; return SUCCEED; } /****************************************************************************** * * * Purpose: copies lowercase converted string to a buffer * * * * Parameters: dst - [OUT] the output buffer * * size - [IN] the output buffer size * * src - [IN] the source string to copy * * len - [IN] the length of the source string * * * * Return value: The number of bytes copied. * * * ******************************************************************************/ static int str_copy_lowercase(char *dst, int size, const char *src, int len) { int i; if (0 == size) return 0; if (size > len + 1) size = len + 1; for (i = 0; i < size - 1 && '\0' != *src; i++) *dst++ = tolower(*src++); *dst = '\0'; return i; } /****************************************************************************** * * * Purpose: parses metric value * * * * Parameters: data - [IN] the source string * * pos - [IN] the starting position * * loc - [OUT] the value location in the source string * * * * Return value: SUCCEED - the metric value was parsed out successfully * * FAIL - otherwise * * * ******************************************************************************/ static int parse_metric_value(const char *data, size_t pos, zbx_strloc_t *loc) { const char *ptr = data + pos; int len; char buffer[4]; loc->l = pos; len = ZBX_CONST_STRLEN("nan"); if (len == str_copy_lowercase(buffer, sizeof(buffer), ptr, len) && 0 == memcmp(buffer, "nan", (size_t)len)) { loc->r = pos + 2; return SUCCEED; } if ('-' == *ptr || '+' == *ptr) ptr++; len = ZBX_CONST_STRLEN("inf"); if (len == str_copy_lowercase(buffer, sizeof(buffer), ptr, len) && 0 == memcmp(buffer, "inf", (size_t)len)) { loc->r = (size_t)(ptr - data) + 2; return SUCCEED; } if (FAIL == zbx_number_parse(ptr, &len)) return FAIL; loc->r = (size_t)(ptr + len - data) - 1; return SUCCEED; } static void prometheus_condition_free(zbx_prometheus_condition_t *condition) { zbx_free(condition->key); zbx_free(condition->pattern); zbx_free(condition); } /****************************************************************************** * * * Purpose: allocates and initializes condition * * * * Parameters: key - [IN] the key to match * * pattern - [IN] the matching pattern * * op - [IN] the matching operation * * * * Return value: the created condition object * * * ******************************************************************************/ static zbx_prometheus_condition_t *prometheus_condition_create(char *key, char *pattern, zbx_prometheus_condition_op_t op) { zbx_prometheus_condition_t *condition; condition = (zbx_prometheus_condition_t *)zbx_malloc(NULL, sizeof(zbx_prometheus_condition_t)); condition->key = key; condition->pattern = pattern; condition->op = op; return condition; } /****************************************************************************** * * * Purpose: clears resources allocated by prometheus filter * * * * Parameters: filter - [IN] the filter to clear * * * ******************************************************************************/ static void prometheus_filter_clear(zbx_prometheus_filter_t *filter) { if (NULL != filter->metric) prometheus_condition_free(filter->metric); if (NULL != filter->value) prometheus_condition_free(filter->value); zbx_vector_prometheus_condition_clear_ext(&filter->labels, prometheus_condition_free); zbx_vector_prometheus_condition_destroy(&filter->labels); } /****************************************************************************** * * * Purpose: parses condition data - key, pattern and operation * * * * Parameters: data - [IN] the filter data * * pos - [IN] the starting position in filter data * * loc_key - [IN] the condition key location * * loc_op - [IN] the condition operation location * * loc_pattern - [IN] the condition pattern location * * * * Return value: SUCCEED - the condition data was parsed successfully * * FAIL - otherwise * * * ******************************************************************************/ static int parse_condition(const char *data, size_t pos, zbx_strloc_t *loc_key, zbx_strloc_t *loc_op, zbx_strloc_t *loc_pattern) { if (SUCCEED != parse_label(data, pos, loc_key)) return FAIL; pos = skip_spaces(data, loc_key->r + 1); if (SUCCEED != parse_label_op(data, pos, loc_op)) return FAIL; pos = skip_spaces(data, loc_op->r + 1); if (SUCCEED != parse_label_value(data, pos, loc_pattern)) return FAIL; return SUCCEED; } /****************************************************************************** * * * Purpose: parses label conditions * * * * Parameters: filter - [IN/OUT] the filter * * data - [IN] the filter data * * pos - [IN] the starting position in filter data * * loc - [IN] the location of label conditions * * error - [IN] the error message * * * * Return value: SUCCEED - the label conditions were parsed successfully * * FAIL - otherwise * * * ******************************************************************************/ static int prometheus_filter_parse_labels(zbx_prometheus_filter_t *filter, const char *data, size_t pos, zbx_strloc_t *loc, char **error) { zbx_strloc_t loc_key, loc_value, loc_op; loc->l = pos; pos = skip_spaces(data, pos + 1); while ('}' != data[pos]) { if (FAIL == parse_condition(data, pos, &loc_key, &loc_op, &loc_value)) { *error = zbx_dsprintf(*error, "cannot parse label condition at \"%s\"", data + pos); return FAIL; } if (0 == str_loc_cmp(data, &loc_key, "__name__", ZBX_CONST_STRLEN("__name__"))) { if (NULL != filter->metric) { *error = zbx_strdup(*error, "duplicate metric condition specified"); return FAIL; } filter->metric = prometheus_condition_create(NULL, str_loc_unquote_dyn(data, &loc_value), str_loc_op(data, &loc_op)); } else { zbx_prometheus_condition_t *condition; condition = prometheus_condition_create(str_loc_dup(data, &loc_key), str_loc_unquote_dyn(data, &loc_value), str_loc_op(data, &loc_op)); zbx_vector_prometheus_condition_append(&filter->labels, condition); } pos = skip_spaces(data, loc_value.r + 1); if (',' != data[pos]) { if ('}' == data[pos]) break; *error = zbx_strdup(*error, "missing label condition list terminating character \"}\""); return FAIL; } pos = skip_spaces(data, pos + 1); } loc->r = pos; return SUCCEED; } /****************************************************************************** * * * Purpose: initializes prometheus pattern filter from the specified data * * * * Parameters: filter - [IN/OUT] the filter * * data - [IN] the filter data * * error - [IN] the error message * * * * Return value: SUCCEED - the filter was initialized successfully * * FAIL - otherwise * * * ******************************************************************************/ static int prometheus_filter_init(zbx_prometheus_filter_t *filter, const char *data, char **error) { int ret = FAIL; size_t pos = 0; zbx_strloc_t loc; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); memset(filter, 0, sizeof(zbx_prometheus_filter_t)); zbx_vector_prometheus_condition_create(&filter->labels); if (NULL == data) return SUCCEED; pos = skip_spaces(data, pos); if (SUCCEED == parse_metric(data, pos, &loc)) { filter->metric = prometheus_condition_create(NULL, str_loc_dup(data, &loc), ZBX_PROMETHEUS_CONDITION_OP_EQUAL); pos = skip_spaces(data, loc.r + 1); } if ('{' == data[pos]) { if (SUCCEED != prometheus_filter_parse_labels(filter, data, pos, &loc, error)) goto out; pos = loc.r + 1; } pos = skip_spaces(data, pos); /* parse metric value condition */ if ('\0' != data[pos]) { zbx_strloc_t loc_op, loc_value; if (SUCCEED != parse_metric_op(data, pos, &loc_op)) { *error = zbx_dsprintf(*error, "cannot parse metric comparison operator at \"%s\"", data + pos); goto out; } pos = skip_spaces(data, loc_op.r + 1); if (SUCCEED != parse_metric_value(data, pos, &loc_value)) { *error = zbx_dsprintf(*error, "cannot parse metric comparison value at \"%s\"", data + pos); goto out; } pos = skip_spaces(data, loc_value.r + 1); if ('\0' != data[pos]) { *error = zbx_dsprintf(*error, "unexpected data after metric comparison value at \"%s\"", data + pos); goto out; } filter->value = prometheus_condition_create(NULL, str_loc_dup(data, &loc_value), ZBX_PROMETHEUS_CONDITION_OP_EQUAL_VALUE); zbx_strlower(filter->value->pattern); } ret = SUCCEED; out: if (FAIL == ret) { prometheus_filter_clear(filter); zabbix_log(LOG_LEVEL_DEBUG, "%s() Prometheus pattern error: %s", __func__, *error); } zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); return ret; } static void prometheus_label_free(zbx_prometheus_label_t *label) { zbx_free(label->name); zbx_free(label->value); zbx_free(label); } static void prometheus_row_free(zbx_prometheus_row_t *row) { zbx_free(row->metric); zbx_free(row->value); zbx_free(row->raw); zbx_vector_prometheus_label_clear_ext(&row->labels, prometheus_label_free); zbx_vector_prometheus_label_destroy(&row->labels); zbx_free(row); } /****************************************************************************** * * * Purpose: matches key,value against filter condition * * * * Parameters: condition - [IN] the condition * * key - [IN] the key (optional, can be NULL) * * value - [IN] the value * * * * Return value: SUCCEED - the key,value pair matches condition * * FAIL - otherwise * * * ******************************************************************************/ static int condition_match_key_value(const zbx_prometheus_condition_t *condition, const char *key, const char *value) { /* perform key match, succeeds if key is not defined in filter */ if (NULL != condition->key && (NULL == key || 0 != strcmp(key, condition->key))) return FAIL; /* match value */ switch (condition->op) { case ZBX_PROMETHEUS_CONDITION_OP_EQUAL: case ZBX_PROMETHEUS_CONDITION_OP_EQUAL_VALUE: if (0 != strcmp(value, condition->pattern)) return FAIL; break; case ZBX_PROMETHEUS_CONDITION_OP_REGEX: if (NULL == zbx_regexp_match(value, condition->pattern, NULL)) return FAIL; break; case ZBX_PROMETHEUS_CONDITION_OP_NOT_EQUAL: if (0 == strcmp(value, condition->pattern)) return FAIL; break; case ZBX_PROMETHEUS_CONDITION_OP_REGEX_NOT_MATCHED: if (NULL != zbx_regexp_match(value, condition->pattern, NULL)) return FAIL; break; default: return FAIL; } return SUCCEED; } /****************************************************************************** * * * Purpose: matches metric value against filter condition * * * * Parameters: pattern - [IN] the condition * * value - [IN] the value * * * * Return value: SUCCEED - the 'value' matches 'condition' * * FAIL - otherwise * * * ******************************************************************************/ static int condition_match_metric_value(const char *pattern, const char *value) { double pattern_dbl, value_dbl; char buffer[5]; if (SUCCEED != zbx_is_double(pattern, &pattern_dbl)) { if ('+' == *pattern) pattern++; if ('+' == *value) value++; zbx_strlcpy(buffer, value, sizeof(buffer)); zbx_strlower(buffer); return (0 == strcmp(pattern, buffer) ? SUCCEED : FAIL); } if (SUCCEED != zbx_is_double(value, &value_dbl)) return FAIL; if (zbx_get_double_epsilon() <= fabs(pattern_dbl - value_dbl)) return FAIL; return SUCCEED; } /****************************************************************************** * * * Purpose: parses metric labels * * * * Parameters: data - [IN] the metric data * * pos - [IN] the starting position in metric data * * labels - [OUT] the parsed labels * * loc - [OUT] the location of label block * * error - [OUT] the error message * * * * Return value: SUCCEED - the labels were parsed successfully * * FAIL - otherwise * * * ******************************************************************************/ static int prometheus_metric_parse_labels(const char *data, size_t pos, zbx_vector_prometheus_label_t *labels, zbx_strloc_t *loc, char **error) { zbx_strloc_t loc_key, loc_value, loc_op; zbx_prometheus_label_t *label; pos = skip_spaces(data, pos + 1); loc->l = pos; while ('}' != data[pos]) { zbx_prometheus_condition_op_t op; if (FAIL == parse_condition(data, pos, &loc_key, &loc_op, &loc_value)) { *error = zbx_strdup(*error, "cannot parse label"); return FAIL; } op = str_loc_op(data, &loc_op); if (ZBX_PROMETHEUS_CONDITION_OP_EQUAL != op) { *error = zbx_strdup(*error, "invalid label assignment operator"); return FAIL; } label = (zbx_prometheus_label_t *)zbx_malloc(NULL, sizeof(zbx_prometheus_label_t)); label->name = str_loc_dup(data, &loc_key); label->value = str_loc_unquote_dyn(data, &loc_value); zbx_vector_prometheus_label_append(labels, label); pos = skip_spaces(data, loc_value.r + 1); if (',' != data[pos]) { if ('}' == data[pos]) break; *error = zbx_strdup(*error, "missing label list terminating character \"}\""); return FAIL; } pos = skip_spaces(data, pos + 1); } loc->r = pos; return SUCCEED; } /****************************************************************************** * * * Purpose: parses metric row * * * * Parameters: filter - [IN] the prometheus filter * * data - [IN] the metric data * * pos - [IN] the starting position in metric data * * prow - [OUT] the parsed row (NULL if did not match filter) * * loc_row - [OUT] the location of row in prometheus data * * error - [OUT] the error message * * * * Return value: SUCCEED - the row was parsed successfully * * FAIL - otherwise * * * * Comments: If there were no parsing errors, but the row does not match * * filter conditions then success with NULL prow is returned. * * * ******************************************************************************/ static int 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) { zbx_strloc_t loc; zbx_prometheus_row_t *row; int ret = FAIL, match = SUCCEED, i, j; loc_row->l = pos; row = (zbx_prometheus_row_t *)zbx_malloc(NULL, sizeof(zbx_prometheus_row_t)); memset(row, 0, sizeof(zbx_prometheus_row_t)); zbx_vector_prometheus_label_create(&row->labels); /* parse metric and check against the filter */ if (SUCCEED != parse_metric(data, pos, &loc)) { *error = zbx_strdup(*error, "cannot parse metric name"); goto out; } row->metric = str_loc_dup(data, &loc); if (NULL != filter->metric) { if (FAIL == (match = condition_match_key_value(filter->metric, NULL, row->metric))) goto out; } /* parse labels and check against the filter */ pos = skip_spaces(data, loc.r + 1); if ('{' == data[pos]) { if (SUCCEED != prometheus_metric_parse_labels(data, pos, &row->labels, &loc, error)) goto out; for (i = 0; i < filter->labels.values_num; i++) { zbx_prometheus_condition_t *condition = filter->labels.values[i]; for (j = 0; j < row->labels.values_num; j++) { zbx_prometheus_label_t *label = row->labels.values[j]; if (SUCCEED == condition_match_key_value(condition, label->name, label->value)) break; } if (j == row->labels.values_num) { /* no matching labels */ match = FAIL; goto out; } } pos = skip_spaces(data, loc.r + 1); } else /* no labels in row */ { if (0 < filter->labels.values_num) /* got labels in filter */ { match = FAIL; goto out; } } /* check if there was a whitespace before metric value */ if (pos == loc.r + 1) { *error = zbx_strdup(*error, "no space before metric value"); goto out; } /* parse value and check against the filter */ if (FAIL == parse_metric_value(data, pos, &loc)) { *error = zbx_strdup(*error, "cannot parse metric value"); goto out; } row->value = str_loc_dup(data, &loc); if (NULL != filter->value) { if (SUCCEED != (match = condition_match_metric_value(filter->value->pattern, row->value))) goto out; } pos = loc.r + 1; if (' ' != data[pos] && '\t' != data[pos] && '\n' != data[pos] && '\0' != data[pos]) { *error = zbx_dsprintf(*error, "invalid character '%c' following metric value", data[pos]); goto out; } /* row was successfully parsed and matched all filter conditions */ ret = SUCCEED; out: if (FAIL == ret) { prometheus_row_free(row); *prow = NULL; /* match failure, return success with NULL row */ if (FAIL == match) ret = SUCCEED; } else *prow = row; if (SUCCEED == ret) { /* find the row location */ pos = skip_row(data, pos); if ('\n' == data[--pos]) pos--; loc_row->r = pos; } return ret; } /****************************************************************************** * * * Purpose: parses HELP comment metric and help text * * * * Parameters: data - [IN] the prometheus data * * pos - [IN] the starting position in metric data * * loc_metric - [OUT] the metric location in data * * loc_help - [OUT] the help location in data * * * * Return value: SUCCEED - the help hint was parsed successfully * * FAIL - otherwise * * * ******************************************************************************/ static int parse_help(const char *data, size_t pos, zbx_strloc_t *loc_metric, zbx_strloc_t *loc_help) { const char *ptr; if (SUCCEED != parse_metric(data, pos, loc_metric)) return FAIL; pos = skip_spaces(data, loc_metric->r + 1); loc_help->l = pos; for (ptr = data + pos; '\0' != *ptr && '\n' != *ptr;) { if ('\\' == *ptr++) { if ('\\' != *ptr && 'n' != *ptr) return FAIL; ptr++; } } loc_help->r = (size_t)(ptr - data) - 1; return SUCCEED; } /****************************************************************************** * * * Purpose: parses TYPE comment metric and the type * * * * Parameters: data - [IN] the prometheus data * * pos - [IN] the starting position in metric data * * loc_metric - [OUT] the metric location in data * * loc_type - [OUT] the type location in data * * * * Return value: SUCCEED - the type hint was parsed successfully * * FAIL - otherwise * * * ******************************************************************************/ static int parse_type(const char *data, size_t pos, zbx_strloc_t *loc_metric, zbx_strloc_t *loc_type) { const char *ptr; if (SUCCEED != parse_metric(data, pos, loc_metric)) return FAIL; pos = skip_spaces(data, loc_metric->r + 1); loc_type->l = pos; ptr = data + pos; while (0 != isalpha(*ptr)) ptr++; /* invalid metric type */ if (pos == (loc_type->r = (size_t)(ptr - data))) return FAIL; loc_type->r--; return SUCCEED; } /****************************************************************************** * * * Purpose: registers TYPE/HELP comment hint to the specified metric * * * * Parameters: hints - [IN/OUT] the hint registry * * data - [IN] the prometheus data * * metric - [IN] the metric * * loc_hint - [IN] the hint location in prometheus data * * hint_type - [IN] the hint type * * error - [OUT] the error message * * * * Return value: SUCCEED - the hint was registered successfully * * FAIL - otherwise * * * ******************************************************************************/ static int prometheus_register_hint(zbx_hashset_t *hints, const char *data, char *metric, const zbx_strloc_t *loc_hint, int hint_type, char **error) { zbx_prometheus_hint_t *hint, hint_local; zbx_strloc_t loc = *loc_hint; hint_local.metric = metric; if (NULL == (hint = (zbx_prometheus_hint_t *)zbx_hashset_search(hints, &hint_local))) { hint = zbx_hashset_insert(hints, &hint_local, sizeof(hint_local)); hint->type = NULL; hint->help = NULL; } else zbx_free(metric); while ((' ' == data[loc.r] || '\t' == data[loc.r]) && loc.r > loc.l) loc.r--; if (ZBX_PROMETHEUS_HINT_HELP == hint_type) { if (NULL != hint->help) { *error = zbx_dsprintf(*error, "multiple HELP comments found for metric \"%s\"", hint->metric); return FAIL; } hint->help = str_loc_unescape_hint_dyn(data, &loc); } else /* ZBX_PROMETHEUS_HINT_TYPE */ { if (NULL != hint->type) { *error = zbx_dsprintf(*error, "multiple TYPE comments found for metric \"%s\"", hint->metric); return FAIL; } hint->type = str_loc_dup(data, &loc); } return SUCCEED; } /****************************************************************************** * * * Purpose: parses TYPE/HELP comment hint and registers it * * * * Parameters: filter - [IN] the prometheus filter * * data - [IN] the prometheus data * * pos - [IN] the position of comments in prometheus data * * hints - [IN/OUT] the hint registry * * loc - [OUT] the location of hint * error - [OUT] the error message * * * * Return value: SUCCEED - the hint was registered successfully * * FAIL - otherwise * * * ******************************************************************************/ static int prometheus_parse_hint(zbx_prometheus_filter_t *filter, const char *data, size_t pos, zbx_hashset_t *hints, zbx_strloc_t *loc, char **error) { int ret, hint_type; zbx_strloc_t loc_metric, loc_hint; char *metric; loc->l = pos; pos = skip_spaces(data, pos + 1); if ('\0' == data[pos]) { loc->r = pos - 1; return SUCCEED; } if (0 == strncmp(data + pos, "HELP", 4)) { pos = skip_spaces(data, pos + 4); ret = parse_help(data, pos, &loc_metric, &loc_hint); hint_type = ZBX_PROMETHEUS_HINT_HELP; } else if (0 == strncmp(data + pos, "TYPE", 4)) { pos = skip_spaces(data, pos + 4); ret = parse_type(data, pos, &loc_metric, &loc_hint); hint_type = ZBX_PROMETHEUS_HINT_TYPE; } else { /* skip the comment */ const char *ptr; if (NULL != (ptr = strchr(data + pos, '\n'))) loc->r = (size_t)(ptr - data) - 1; else loc->r = strlen(data + pos) + pos - 1; return SUCCEED; } if (SUCCEED != ret) { *error = zbx_strdup(*error, "cannot parse comment"); return FAIL; } loc->r = loc_hint.r; metric = str_loc_dup(data, &loc_metric); /* skip hints of metrics not matching filter */ if (NULL != filter->metric && SUCCEED != condition_match_key_value(filter->metric, NULL, metric)) { zbx_free(metric); return SUCCEED; } return prometheus_register_hint(hints, data, metric, &loc_hint, hint_type, error); } /****************************************************************************** * * * Purpose: parses rows with metrics from prometheus data * * * * Parameters: filter - [IN] the prometheus filter * * data - [IN] the metric data * * rows - [OUT] the parsed rows * * hints - [OUT] the TYPE/HELP hint registry (optional) * * error - [OUT] the error message * * * * Return value: SUCCEED - the rows were parsed successfully * * FAIL - otherwise * * * ******************************************************************************/ static int prometheus_parse_rows(zbx_prometheus_filter_t *filter, const char *data, zbx_vector_prometheus_row_t *rows, zbx_hashset_t *hints, char **error) { size_t pos = 0; int row_num = 1, ret = FAIL; zbx_prometheus_row_t *row; char *errmsg = NULL; zbx_strloc_t loc; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); for (pos = 0; '\0' != data[pos]; pos = skip_row(data, pos), row_num++) { pos = skip_spaces(data, pos); /* skip empty strings */ if ('\n' == data[pos]) continue; if ('#' == data[pos]) { if (NULL != hints) { if (SUCCEED != prometheus_parse_hint(filter, data, pos, hints, &loc, &errmsg)) goto out; pos = loc.r + 1; } continue; } if (SUCCEED != prometheus_parse_row(filter, data, pos, &row, &loc, &errmsg)) goto out; if (NULL != row) { row->raw = str_loc_dup(data, &loc); zbx_vector_prometheus_row_append(rows, row); } pos = loc.r + 1; } ret = SUCCEED; out: if (SUCCEED != ret) { const char *ptr, *suffix = ""; int len; if (NULL != (ptr = strchr(data + pos, '\n'))) len = (size_t)(ptr - data) - pos; else len = strlen(data + pos); /* Defines maximum row length to be written in error message in the case of parsing failure */ #define ZBX_PROMEHTEUS_ERROR_MAX_ROW_LENGTH 50 if (ZBX_PROMEHTEUS_ERROR_MAX_ROW_LENGTH < len) { len = ZBX_PROMEHTEUS_ERROR_MAX_ROW_LENGTH; suffix = "..."; } *error = zbx_dsprintf(*error, "data parsing error at row %d \"%.*s%s\": %s", row_num, len, data + pos, suffix, errmsg); zbx_free(errmsg); #undef ZBX_PROMEHTEUS_ERROR_MAX_ROW_LENGTH } zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s rows:%d hints:%d", __func__, zbx_result_string(ret), rows->values_num, (NULL == hints ? 0 : hints->num_data)); return ret; } /****************************************************************************** * * * Purpose: extracts value from row * * * * Parameters: rows - [IN] the source rows * * output - [IN] the output template * * value - [OUT] the extracted value * * error - [OUT] the error message * * * * Return value: SUCCEED - the value was extracted successfully * * FAIL - otherwise * * * ******************************************************************************/ static int prometheus_extract_value(const zbx_vector_prometheus_row_t *rows, const char *output, char **value, char **error) { const zbx_prometheus_row_t *row; if (0 == rows->values_num) { *error = zbx_strdup(*error, "no matching metrics found"); return FAIL; } if (1 < rows->values_num) { #define ZBX_PROMETHEUS_ERROR_ROW_NUM 10 int i, rows_num = ZBX_PROMETHEUS_ERROR_ROW_NUM; size_t error_alloc, error_offset = 0; error_alloc = (NULL == *error ? 0 : strlen(*error) + 1); zbx_strcpy_alloc(error, &error_alloc, &error_offset, "multiple matching metrics found:\n\n"); if (rows->values_num < rows_num) rows_num = rows->values_num; for (i = 0; i < rows_num; i++) { row = rows->values[i]; zbx_strcpy_alloc(error, &error_alloc, &error_offset, row->raw); zbx_chrcpy_alloc(error, &error_alloc, &error_offset, '\n'); } if (rows->values_num > rows_num) zbx_strcpy_alloc(error, &error_alloc, &error_offset, "..."); else (*error)[error_offset - 1] = '\0'; return FAIL; #undef ZBX_PROMETHEUS_ERROR_ROW_NUM } row = rows->values[0]; if ('\0' != *output) { int i; for (i = 0; i < row->labels.values_num; i++) { const zbx_prometheus_label_t *label = (const zbx_prometheus_label_t *)row->labels.values[i]; if (0 == strcmp(label->name, output)) { *value = zbx_strdup(NULL, label->value); break; } } if (i == row->labels.values_num) { *error = zbx_strdup(*error, "no label matches the specified output"); return FAIL; } } else *value = zbx_strdup(NULL, row->value); return SUCCEED; } /****************************************************************************** * * * Purpose: aggregates row values * * * * Parameters: rows - [IN] the source rows * * function - [IN] the aggregation function * * value - [OUT] the aggregated value * * error - [OUT] the error message * * * * Return value: SUCCEED - the values were aggregated successfully * * FAIL - otherwise * * * ******************************************************************************/ static int prometheus_aggregate_values(const zbx_vector_prometheus_row_t *rows, const char *function, char **value, char **error) { zbx_vector_dbl_t values; int i, ret; double value_dbl; const zbx_prometheus_row_t *row; zbx_vector_dbl_create(&values); for (i = 0; i < rows->values_num; i++) { row = rows->values[i]; value_dbl = atof(row->value); if (0 == isnan(value_dbl)) zbx_vector_dbl_append(&values, value_dbl); } if (0 == strcmp(function, "avg")) { ret = zbx_eval_calc_avg(&values, &value_dbl, error); } else if (0 == strcmp(function, "min")) { ret = zbx_eval_calc_min(&values, &value_dbl, error); } else if (0 == strcmp(function, "max")) { ret = zbx_eval_calc_max(&values, &value_dbl, error); } else if (0 == strcmp(function, "sum")) { ret = zbx_eval_calc_sum(&values, &value_dbl, error); } else if (0 == strcmp(function, "count")) { value_dbl = (double)values.values_num; ret = SUCCEED; } else { *error = zbx_dsprintf(NULL, "unsupported aggregation function \"%s\"", function); ret = FAIL; } zbx_vector_dbl_destroy(&values); if (SUCCEED == ret) { char buffer[32]; zbx_print_double(buffer, sizeof(buffer), value_dbl); *value = zbx_strdup(NULL, buffer); } return ret; } /****************************************************************************** * * * Purpose: performs the specified request on rows * * * * Parameters: rows - [IN] the source rows * * request - [IN] the request (value, label, function) * * output - [IN] the output template/function name * * value - [OUT] the result value * * error - [OUT] the error message * * * * Return value: SUCCEED - the request was performed successfully * * FAIL - otherwise * * * ******************************************************************************/ static int prometheus_query_rows(const zbx_vector_prometheus_row_t *rows, const char *request, const char *output, char **value, char **error) { if (0 == strcmp(request, "function")) return prometheus_aggregate_values(rows, output, value, error); return prometheus_extract_value(rows, output, value, error); } /****************************************************************************** * * * Purpose: get rows matching the filter criteria * * * * Parameters: rows - [IN] the rows to filter * * filter - [IN] the prometheus filt * * rows_out - [OUT] the filtered rows * * * ******************************************************************************/ static void prometheus_filter_rows(zbx_vector_prometheus_row_t *rows, zbx_prometheus_filter_t *filter, zbx_vector_prometheus_row_t *rows_out) { int i, j, k; zbx_prometheus_row_t *row; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); for (i = 0; i < rows->values_num; i++) { row = rows->values[i]; if (NULL != filter->metric) { if (SUCCEED != condition_match_key_value(filter->metric, NULL, row->metric)) continue; } if (0 == row->labels.values_num && 0 != filter->labels.values_num) continue; for (j = 0; j < filter->labels.values_num; j++) { zbx_prometheus_condition_t *condition = filter->labels.values[j]; for (k = 0; k < row->labels.values_num; k++) { zbx_prometheus_label_t *label = row->labels.values[k]; if (SUCCEED == condition_match_key_value(condition, label->name, label->value)) break; } if (k == row->labels.values_num) break; } if (j != filter->labels.values_num) continue; if (NULL != filter->value) { if (SUCCEED != condition_match_metric_value(filter->value->pattern, row->value)) continue; } zbx_vector_prometheus_row_append(rows_out, row); } zabbix_log(LOG_LEVEL_DEBUG, "End of %s() rows:%d", __func__, rows_out->values_num); } /****************************************************************************** * * * Purpose: parse prometheus input and initialize cache * * * * Parameters: prom - [IN] the prometheus cache * * data - [IN] the prometheus data * * error - [OUT] the error message rows * * * * Return value: SUCCEED - the prometheus data were parsed successfully * * FAIL - otherwise * * * ******************************************************************************/ int zbx_prometheus_init(zbx_prometheus_t *prom, const char *data, char **error) { zbx_prometheus_filter_t filter = {0}; int ret = FAIL; zbx_vector_prometheus_row_create(&prom->rows); zbx_vector_prometheus_label_index_create(&prom->indexes); if (0 != pthread_rwlock_init(&prom->index_lock, NULL)) { *error = zbx_dsprintf(NULL, "Cannot initialize prometheus cache: %s", zbx_strerror(errno)); goto out; } if (SUCCEED != prometheus_filter_init(&filter, NULL, error)) goto out; if (FAIL == prometheus_parse_rows(&filter, data, &prom->rows, NULL, error)) goto out; ret = SUCCEED; out: prometheus_filter_clear(&filter); if (SUCCEED != ret) zbx_prometheus_clear(prom); return ret; } static void prometheus_label_index_free(zbx_prometheus_label_index_t *label_index) { zbx_hashset_iter_t iter; zbx_prometheus_index_t *index; zbx_free(label_index->label); zbx_hashset_iter_reset(&label_index->index, &iter); while (NULL != (index = (zbx_prometheus_index_t *)zbx_hashset_iter_next(&iter))) zbx_vector_prometheus_row_destroy(&index->rows); zbx_hashset_destroy(&label_index->index); zbx_free(label_index); } /****************************************************************************** * * * Purpose: free resources allocated by prometheus cache * * * * Parameters: prom - [IN] the prometheus cache * * * ******************************************************************************/ void zbx_prometheus_clear(zbx_prometheus_t *prom) { zbx_vector_prometheus_label_index_clear_ext(&prom->indexes, prometheus_label_index_free); zbx_vector_prometheus_label_index_destroy(&prom->indexes); zbx_vector_prometheus_row_clear_ext(&prom->rows, prometheus_row_free); zbx_vector_prometheus_row_destroy(&prom->rows); pthread_rwlock_destroy(&prom->index_lock); } static void prometheus_wrlock(zbx_prometheus_t *prom) { if (0 != pthread_rwlock_wrlock(&prom->index_lock)) { zabbix_log(LOG_LEVEL_CRIT, "Cannot lock prometheus cache for writing: %s", zbx_strerror(errno)); THIS_SHOULD_NEVER_HAPPEN; exit(EXIT_FAILURE); } } static void prometheus_rdlock(zbx_prometheus_t *prom) { if (0 != pthread_rwlock_rdlock(&prom->index_lock)) { zabbix_log(LOG_LEVEL_CRIT, "Cannot lock prometheus cache for reading: %s", zbx_strerror(errno)); THIS_SHOULD_NEVER_HAPPEN; exit(EXIT_FAILURE); } } static void prometheus_unlock(zbx_prometheus_t *prom) { if (0 != pthread_rwlock_unlock(&prom->index_lock)) { zabbix_log(LOG_LEVEL_CRIT, "Cannot unlock prometheus cache: %s", zbx_strerror(errno)); THIS_SHOULD_NEVER_HAPPEN; exit(EXIT_FAILURE); } } /****************************************************************************** * * * row indexing support * * * ******************************************************************************/ static zbx_prometheus_label_index_t *prometheus_get_index(zbx_prometheus_t *prom, const char *label) { int i; zbx_prometheus_label_index_t *label_index = NULL; prometheus_rdlock(prom); for (i = 0; i < prom->indexes.values_num; i++) { if (0 == strcmp(prom->indexes.values[i]->label, label)) { label_index = prom->indexes.values[i]; break; } } prometheus_unlock(prom); return label_index; } static void prometheus_add_index(zbx_prometheus_t *prom, zbx_prometheus_label_index_t *index) { prometheus_wrlock(prom); zbx_vector_prometheus_label_index_append(&prom->indexes, index); prometheus_unlock(prom); } static zbx_hash_t prometheus_index_hash_func(const void *d) { const zbx_prometheus_index_t *index = (const zbx_prometheus_index_t *)d; return ZBX_DEFAULT_STRING_HASH_FUNC(index->value); } static int prometheus_index_compare_func(const void *d1, const void *d2) { const zbx_prometheus_index_t *i1 = (const zbx_prometheus_index_t *)d1; const zbx_prometheus_index_t *i2 = (const zbx_prometheus_index_t *)d2; return strcmp(i1->value, i2->value); } /****************************************************************************** * * * Purpose: get label from row by the specified name * * * * Parameters: row - [IN] the prometheus row * * name - [IN] the label name * * * * Return value: The prometheus row label or NULL if no labels matched the * * specified name. * * * ******************************************************************************/ static zbx_prometheus_label_t *prometheus_get_row_label(zbx_prometheus_row_t *row, const char *name) { int i; for (i = 0; i < row->labels.values_num; i++) { zbx_prometheus_label_t *label = row->labels.values[i]; if (0 == strcmp(label->name, name)) return label; } return NULL; } /****************************************************************************** * * * Purpose: get rows matching one filter label * * * * Parameters: prom - [IN] the prometheus cache * * filter - [IN] the filter * * rows - [OUT] the rows matching filter label or NULL if there * * are now matching rows * * * * Return value: SUCCEED - the matched rows were returned successfully * * FAIL - filter does not contain conditions that can be * * indexed. * * * * Comments: The rows are indexed by first filter 'label equals' condition. * * The index is created automatically when rows for unindexed * * label are requested. * * * ******************************************************************************/ static int prometheus_get_indexed_rows_by_label(zbx_prometheus_t *prom, zbx_prometheus_filter_t *filter, zbx_vector_prometheus_row_t **rows) { int i; zbx_prometheus_condition_t *condition; zbx_prometheus_label_index_t *label_index; zbx_prometheus_index_t *index, index_local; for (i = 0; i < filter->labels.values_num; i++) { condition = filter->labels.values[i]; if (ZBX_PROMETHEUS_CONDITION_OP_EQUAL == condition->op) break; } if (i == filter->labels.values_num) return FAIL; if (NULL == (label_index = prometheus_get_index(prom, condition->key))) { label_index = (zbx_prometheus_label_index_t *)zbx_malloc(NULL, sizeof(zbx_prometheus_label_index_t)); label_index->label = zbx_strdup(NULL, condition->key); zbx_hashset_create(&label_index->index, 0, prometheus_index_hash_func, prometheus_index_compare_func); for (i = 0; i < prom->rows.values_num; i++) { zbx_prometheus_row_t *row = prom->rows.values[i]; zbx_prometheus_label_t *label; if (NULL == (label = prometheus_get_row_label(row, label_index->label))) continue; index_local.value = label->value; if (NULL == (index = (zbx_prometheus_index_t *)zbx_hashset_search(&label_index->index, &index_local))) { index = (zbx_prometheus_index_t *)zbx_hashset_insert(&label_index->index, &index_local, sizeof(index_local)); zbx_vector_prometheus_row_create(&index->rows); } zbx_vector_prometheus_row_append(&index->rows, row); } prometheus_add_index(prom, label_index); } index_local.value = condition->pattern; if (NULL != (index = (zbx_prometheus_index_t *)zbx_hashset_search(&label_index->index, &index_local))) *rows = &index->rows; else *rows = NULL; return SUCCEED; } /****************************************************************************** * * * Purpose: validate prometheus pattern request and output * * * * Parameters: request - [IN] the prometheus request * * output - [IN] the prometheus output * * error - [OUT] the error message * * * * Return value: SUCCEED - valid request and output combination * * FAIL - invalid request and output combination * * * ******************************************************************************/ static int prometheus_validate_request(const char *request, const char *output, char **error) { if (0 == strcmp(request, "value")) { if ('\0' != *output) { *error = zbx_strdup(NULL, "invalid third parameter"); return FAIL; } return SUCCEED; } if ('\0' == *output) { *error = zbx_strdup(NULL, "missing third parameter"); return FAIL; } return SUCCEED; } /****************************************************************************** * * * Purpose: extract value from prometheus cache by the specified filter * * * * Parameters: prom - [IN] the prometheus cache * * filter_data - [IN] the filter in text format * * request - [IN] the data request - value, label, function * * output - [IN] the output template/function name * * value - [OUT] the extracted value * * error - [OUT] the error message * * * * Return value: SUCCEED - the value was extracted successfully * * FAIL - otherwise * * * ******************************************************************************/ int zbx_prometheus_pattern_ex(zbx_prometheus_t *prom, const char *filter_data, const char *request, const char *output, char **value, char **error) { zbx_prometheus_filter_t filter; int ret = FAIL; char *errmsg = NULL; zbx_vector_prometheus_row_t rows, *prows; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); if (FAIL == prometheus_filter_init(&filter, filter_data, &errmsg)) { *error = zbx_dsprintf(*error, "pattern error: %s", errmsg); zbx_free(errmsg); goto out; } zbx_vector_prometheus_row_create(&rows); if (SUCCEED != prometheus_validate_request(request, output, error)) return FAIL; if (SUCCEED != prometheus_get_indexed_rows_by_label(prom, &filter, &prows) || NULL == prows) prows = &prom->rows; prometheus_filter_rows(prows, &filter, &rows); if (FAIL == (ret = prometheus_query_rows(&rows, request, output, value, &errmsg))) { *error = zbx_dsprintf(*error, "data extraction error: %s", errmsg); zbx_free(errmsg); } prometheus_filter_clear(&filter); zbx_vector_prometheus_row_destroy(&rows); out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } /****************************************************************************** * * * Purpose: extracts value from prometheus data by the specified filter * * * * Parameters: data - [IN] the prometheus data * * filter_data - [IN] the filter in text format * * request - [IN] the data request - value, label, function * * output - [IN] the output template/function name * * value - [OUT] the extracted value * * error - [OUT] the error message * * * * Return value: SUCCEED - the value was extracted successfully * * FAIL - otherwise * * * ******************************************************************************/ int zbx_prometheus_pattern(const char *data, const char *filter_data, const char *request, const char *output, char **value, char **error) { zbx_prometheus_filter_t filter; char *errmsg = NULL; int ret = FAIL; zbx_vector_prometheus_row_t rows; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); if (FAIL == prometheus_filter_init(&filter, filter_data, &errmsg)) { *error = zbx_dsprintf(*error, "pattern error: %s", errmsg); zbx_free(errmsg); goto out; } zbx_vector_prometheus_row_create(&rows); if (SUCCEED != prometheus_validate_request(request, output, error)) return FAIL; if (FAIL == prometheus_parse_rows(&filter, data, &rows, NULL, error)) goto cleanup; if (FAIL == prometheus_query_rows(&rows, request, output, value, &errmsg)) { *error = zbx_dsprintf(*error, "data extraction error: %s", errmsg); zbx_free(errmsg); goto cleanup; } zabbix_log(LOG_LEVEL_DEBUG, "%s(): output:%s", __func__, *value); ret = SUCCEED; cleanup: zbx_vector_prometheus_row_clear_ext(&rows, prometheus_row_free); zbx_vector_prometheus_row_destroy(&rows); prometheus_filter_clear(&filter); out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } /****************************************************************************** * * * Purpose: converts filtered prometheus data to json to be used with LLD * * * * Parameters: data - [IN] the prometheus data * * filter_data - [IN] the filter in text format * * value - [OUT] the converted data * * error - [OUT] the error message * * * * Return value: SUCCEED - the data was converted successfully * * FAIL - otherwise * * * ******************************************************************************/ int zbx_prometheus_to_json(const char *data, const char *filter_data, char **value, char **error) { zbx_prometheus_filter_t filter; char *errmsg = NULL; int ret = FAIL, i, j; zbx_vector_prometheus_row_t rows; zbx_hashset_t hints; zbx_prometheus_hint_t *hint, hint_local; zbx_hashset_iter_t iter; struct zbx_json json; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); if (FAIL == prometheus_filter_init(&filter, filter_data, &errmsg)) { *error = zbx_dsprintf(*error, "pattern error: %s", errmsg); zbx_free(errmsg); goto out; } zbx_vector_prometheus_row_create(&rows); zbx_hashset_create(&hints, 100, prometheus_hint_hash, prometheus_hint_compare); if (FAIL == prometheus_parse_rows(&filter, data, &rows, &hints, error)) goto cleanup; zbx_json_initarray(&json, (size_t)rows.values_num * 100); for (i = 0; i < rows.values_num; i++) { zbx_prometheus_row_t *row = rows.values[i]; char *hint_type; zbx_json_addobject(&json, NULL); zbx_json_addstring(&json, ZBX_PROTO_TAG_NAME, row->metric, ZBX_JSON_TYPE_STRING); zbx_json_addstring(&json, ZBX_PROTO_TAG_VALUE, row->value, ZBX_JSON_TYPE_STRING); zbx_json_addstring(&json, ZBX_PROTO_TAG_LINE_RAW, row->raw, ZBX_JSON_TYPE_STRING); if (0 != row->labels.values_num) { zbx_json_addobject(&json, ZBX_PROTO_TAG_LABELS); for (j = 0; j < row->labels.values_num; j++) { zbx_prometheus_label_t *label = row->labels.values[j]; zbx_json_addstring(&json, label->name, label->value, ZBX_JSON_TYPE_STRING); } zbx_json_close(&json); } hint_local.metric = row->metric; hint = (zbx_prometheus_hint_t *)zbx_hashset_search(&hints, &hint_local); #define ZBX_PROMETHEUS_TYPE_UNTYPED "untyped" hint_type = (NULL != hint && NULL != hint->type ? hint->type : ZBX_PROMETHEUS_TYPE_UNTYPED); zbx_json_addstring(&json, ZBX_PROTO_TAG_TYPE, hint_type, ZBX_JSON_TYPE_STRING); #undef ZBX_PROMETHEUS_TYPE_UNTYPED if (NULL != hint && NULL != hint->help) zbx_json_addstring(&json, ZBX_PROTO_TAG_HELP, hint->help, ZBX_JSON_TYPE_STRING); zbx_json_close(&json); } *value = zbx_strdup(NULL, json.buffer); zbx_json_free(&json); zabbix_log(LOG_LEVEL_DEBUG, "%s(): output:%s", __func__, *value); ret = SUCCEED; cleanup: zbx_hashset_iter_reset(&hints, &iter); while (NULL != (hint = (zbx_prometheus_hint_t *)zbx_hashset_iter_next(&iter))) { zbx_free(hint->metric); zbx_free(hint->help); zbx_free(hint->type); } zbx_hashset_destroy(&hints); zbx_vector_prometheus_row_clear_ext(&rows, prometheus_row_free); zbx_vector_prometheus_row_destroy(&rows); prometheus_filter_clear(&filter); out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } int zbx_prometheus_validate_filter(const char *pattern, char **error) { zbx_prometheus_filter_t filter; if (FAIL == prometheus_filter_init(&filter, pattern, error)) return FAIL; prometheus_filter_clear(&filter); return SUCCEED; } int zbx_prometheus_validate_label(const char *label) { zbx_strloc_t loc; size_t pos; if ('\0' == *label) return SUCCEED; if (SUCCEED != parse_label(label, 0, &loc)) return FAIL; pos = skip_spaces(label, loc.r + 1); if ('\0' != label[pos]) return FAIL; return SUCCEED; } #ifdef HAVE_TESTS # include "../../../tests/libs/zbxprometheus/prometheus_test.c" #endif