monitoring-plugins/lib/perfdata.c
Lorenz Kästle c57381d789
Revert check_disk performance data back to used space (#2243)
* Implement simple output shortcut for ranges

If ranges start with zero (e.g. 0:10), the zero and the colon
can be left out.

This patch implements this by default, since some systems (icinga2)
do not fully implement the whole range format and this reduces errors
in the common case of just an upper border.

* switch check_disk perfdata back to used space
2026-04-06 11:55:27 +02:00

658 lines
15 KiB
C

#include "./perfdata.h"
#include "../plugins/common.h"
#include "../plugins/utils.h"
#include "utils_base.h"
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
char *pd_value_to_string(const mp_perfdata_value pd) {
char *result = NULL;
assert(pd.type != PD_TYPE_NONE);
switch (pd.type) {
case PD_TYPE_INT:
asprintf(&result, "%lli", pd.pd_int);
break;
case PD_TYPE_UINT:
asprintf(&result, "%llu", pd.pd_int);
break;
case PD_TYPE_DOUBLE:
asprintf(&result, "%f", pd.pd_double);
break;
default:
// die here
die(STATE_UNKNOWN, "Invalid mp_perfdata mode\n");
}
return result;
}
char *pd_to_string(mp_perfdata pd) {
assert(pd.label != NULL);
char *result = NULL;
if (strchr(pd.label, '\'') == NULL) {
asprintf(&result, "'%s'=", pd.label);
} else {
// we have an illegal single quote in the string
// replace it silently instead of complaining
for (char *ptr = pd.label; *ptr != '\0'; ptr++) {
if (*ptr == '\'') {
*ptr = '_';
}
}
}
asprintf(&result, "%s%s", result, pd_value_to_string(pd.value));
if (pd.uom != NULL) {
asprintf(&result, "%s%s", result, pd.uom);
}
if (pd.warn_present) {
asprintf(&result, "%s;%s", result, mp_range_to_string(pd.warn));
} else {
asprintf(&result, "%s;", result);
}
if (pd.crit_present) {
asprintf(&result, "%s;%s", result, mp_range_to_string(pd.crit));
} else {
asprintf(&result, "%s;", result);
}
if (pd.min_present) {
asprintf(&result, "%s;%s", result, pd_value_to_string(pd.min));
} else {
asprintf(&result, "%s;", result);
}
if (pd.max_present) {
asprintf(&result, "%s;%s", result, pd_value_to_string(pd.max));
}
/*printf("pd_to_string: %s\n", result); */
return result;
}
char *pd_list_to_string(const pd_list pd) {
char *result = pd_to_string(pd.data);
for (pd_list *elem = pd.next; elem != NULL; elem = elem->next) {
asprintf(&result, "%s %s", result, pd_to_string(elem->data));
}
return result;
}
mp_perfdata perfdata_init() {
mp_perfdata pd = {};
return pd;
}
pd_list *pd_list_init() {
pd_list *tmp = (pd_list *)calloc(1, sizeof(pd_list));
if (tmp == NULL) {
die(STATE_UNKNOWN, "calloc failed\n");
}
tmp->next = NULL;
return tmp;
}
mp_range mp_range_init() {
mp_range result = {
.alert_on_inside_range = OUTSIDE,
.start = {},
.start_infinity = true,
.end = {},
.end_infinity = true,
};
return result;
}
mp_range mp_range_set_start(mp_range input, mp_perfdata_value perf_val) {
input.start = perf_val;
input.start_infinity = false;
return input;
}
mp_range mp_range_set_end(mp_range input, mp_perfdata_value perf_val) {
input.end = perf_val;
input.end_infinity = false;
return input;
}
void pd_list_append(pd_list pdl[1], const mp_perfdata pd) {
assert(pdl != NULL);
if (pdl->data.value.type == PD_TYPE_NONE) {
// first entry is still empty
pdl->data = pd;
} else {
// find last element in the list
pd_list *curr = pdl;
pd_list *next = pdl->next;
while (next != NULL) {
curr = next;
next = next->next;
}
if (curr->data.value.type == PD_TYPE_NONE) {
// still empty
curr->data = pd;
} else {
// new a new one
curr->next = pd_list_init();
curr->next->data = pd;
}
}
}
void pd_list_free(pd_list pdl[1]) {
while (pdl != NULL) {
pd_list *old = pdl;
pdl = pdl->next;
free(old);
}
}
/*
* returns -1 if a < b, 0 if a == b, 1 if a > b
*/
int cmp_perfdata_value(const mp_perfdata_value a, const mp_perfdata_value b) {
// Test if types are different
if (a.type == b.type) {
switch (a.type) {
case PD_TYPE_UINT:
if (a.pd_uint < b.pd_uint) {
return -1;
} else if (a.pd_uint == b.pd_uint) {
return 0;
} else {
return 1;
}
break;
case PD_TYPE_INT:
if (a.pd_int < b.pd_int) {
return -1;
} else if (a.pd_int == b.pd_int) {
return 0;
} else {
return 1;
}
break;
case PD_TYPE_DOUBLE:
if (a.pd_int < b.pd_int) {
return -1;
} else if (a.pd_int == b.pd_int) {
return 0;
} else {
return 1;
}
break;
default:
die(STATE_UNKNOWN, "Error in %s line: %d!", __FILE__, __LINE__);
}
}
// Get dirty here
long double floating_a = 0;
switch (a.type) {
case PD_TYPE_UINT:
floating_a = a.pd_uint;
break;
case PD_TYPE_INT:
floating_a = a.pd_int;
break;
case PD_TYPE_DOUBLE:
floating_a = a.pd_double;
break;
default:
die(STATE_UNKNOWN, "Error in %s line: %d!", __FILE__, __LINE__);
}
long double floating_b = 0;
switch (b.type) {
case PD_TYPE_UINT:
floating_b = b.pd_uint;
break;
case PD_TYPE_INT:
floating_b = b.pd_int;
break;
case PD_TYPE_DOUBLE:
floating_b = b.pd_double;
break;
default:
die(STATE_UNKNOWN, "Error in %s line: %d!", __FILE__, __LINE__);
}
if (floating_a < floating_b) {
return -1;
}
if (floating_a == floating_b) {
return 0;
}
return 1;
}
char *mp_range_to_string(const mp_range input) {
char *result = "";
if (input.alert_on_inside_range == INSIDE) {
asprintf(&result, "@");
}
if (input.start_infinity) {
asprintf(&result, "%s~:", result);
} else {
// check for zeroes, so we can use the short form
if ((input.start.type == PD_TYPE_NONE) ||
((input.start.type == PD_TYPE_INT) && (input.start.pd_int == 0)) ||
((input.start.type == PD_TYPE_UINT) && (input.start.pd_uint == 0)) ||
((input.start.type == PD_TYPE_DOUBLE) && (input.start.pd_double == 0))){
// nothing to do here
} else {
// Start value is an actual value
asprintf(&result, "%s%s:", result, pd_value_to_string(input.start));
}
}
if (!input.end_infinity) {
asprintf(&result, "%s%s", result, pd_value_to_string(input.end));
}
return result;
}
mp_perfdata mp_set_pd_value_float(mp_perfdata pd, float value) {
return mp_set_pd_value_double(pd, value);
}
mp_perfdata mp_set_pd_value_double(mp_perfdata pd, double value) {
pd.value.pd_double = value;
pd.value.type = PD_TYPE_DOUBLE;
return pd;
}
mp_perfdata mp_set_pd_value_char(mp_perfdata pd, char value) {
return mp_set_pd_value_long_long(pd, (long long)value);
}
mp_perfdata mp_set_pd_value_u_char(mp_perfdata pd, unsigned char value) {
return mp_set_pd_value_u_long_long(pd, (unsigned long long)value);
}
mp_perfdata mp_set_pd_value_int(mp_perfdata pd, int value) {
return mp_set_pd_value_long_long(pd, (long long)value);
}
mp_perfdata mp_set_pd_value_u_int(mp_perfdata pd, unsigned int value) {
return mp_set_pd_value_u_long_long(pd, (unsigned long long)value);
}
mp_perfdata mp_set_pd_value_long(mp_perfdata pd, long value) {
return mp_set_pd_value_long_long(pd, (long long)value);
}
mp_perfdata mp_set_pd_value_u_long(mp_perfdata pd, unsigned long value) {
return mp_set_pd_value_u_long_long(pd, (unsigned long long)value);
}
mp_perfdata mp_set_pd_value_long_long(mp_perfdata pd, long long value) {
pd.value.pd_int = value;
pd.value.type = PD_TYPE_INT;
return pd;
}
mp_perfdata mp_set_pd_value_u_long_long(mp_perfdata pd, unsigned long long value) {
pd.value.pd_uint = value;
pd.value.type = PD_TYPE_UINT;
return pd;
}
mp_perfdata_value mp_create_pd_value_double(double value) {
mp_perfdata_value res = {0};
res.type = PD_TYPE_DOUBLE;
res.pd_double = value;
return res;
}
mp_perfdata_value mp_create_pd_value_float(float value) {
return mp_create_pd_value_double((double)value);
}
mp_perfdata_value mp_create_pd_value_char(char value) {
return mp_create_pd_value_long_long((long long)value);
}
mp_perfdata_value mp_create_pd_value_u_char(unsigned char value) {
return mp_create_pd_value_u_long_long((unsigned long long)value);
}
mp_perfdata_value mp_create_pd_value_int(int value) {
return mp_create_pd_value_long_long((long long)value);
}
mp_perfdata_value mp_create_pd_value_u_int(unsigned int value) {
return mp_create_pd_value_u_long_long((unsigned long long)value);
}
mp_perfdata_value mp_create_pd_value_long(long value) {
return mp_create_pd_value_long_long((long long)value);
}
mp_perfdata_value mp_create_pd_value_u_long(unsigned long value) {
return mp_create_pd_value_u_long_long((unsigned long long)value);
}
mp_perfdata_value mp_create_pd_value_long_long(long long value) {
mp_perfdata_value res = {0};
res.type = PD_TYPE_INT;
res.pd_int = value;
return res;
}
mp_perfdata_value mp_create_pd_value_u_long_long(unsigned long long value) {
mp_perfdata_value res = {0};
res.type = PD_TYPE_UINT;
res.pd_uint = value;
return res;
}
char *fmt_range(range foo) { return foo.text; }
typedef struct integer_parser_wrapper {
int error;
mp_perfdata_value value;
} integer_parser_wrapper;
typedef struct double_parser_wrapper {
int error;
mp_perfdata_value value;
} double_parser_wrapper;
typedef struct perfdata_value_parser_wrapper {
int error;
mp_perfdata_value value;
} perfdata_value_parser_wrapper;
double_parser_wrapper parse_double(const char *input);
integer_parser_wrapper parse_integer(const char *input);
perfdata_value_parser_wrapper parse_pd_value(const char *input);
mp_range_parsed mp_parse_range_string(const char *input) {
if (input == NULL) {
mp_range_parsed result = {
.error = MP_RANGE_PARSING_FAILURE,
};
return result;
}
if (strlen(input) == 0) {
mp_range_parsed result = {
.error = MP_RANGE_PARSING_FAILURE,
};
return result;
}
mp_range_parsed result = {
.range = mp_range_init(),
.error = MP_PARSING_SUCCESS,
};
if (input[0] == '@') {
// found an '@' at beginning, so invert the range logic
result.range.alert_on_inside_range = INSIDE;
// advance the pointer one symbol
input++;
}
char *working_copy = strdup(input);
if (working_copy == NULL) {
// strdup error, probably
mp_range_parsed result = {
.error = MP_RANGE_PARSING_FAILURE,
};
return result;
}
input = working_copy;
char *separator = index(working_copy, ':');
if (separator != NULL) {
// Found a separator
// set the separator to 0, so we have two different strings
*separator = '\0';
if (input[0] == '~') {
// the beginning starts with '~', so it might be infinity
if (&input[1] != separator) {
// the next symbol after '~' is not the separator!
// so input is probably wrong
result.error = MP_RANGE_PARSING_FAILURE;
free(working_copy);
return result;
}
result.range.start_infinity = true;
} else {
// No '~' at the beginning, so this should be a number
result.range.start_infinity = false;
perfdata_value_parser_wrapper parsed_pd = parse_pd_value(input);
if (parsed_pd.error != MP_PARSING_SUCCESS) {
result.error = parsed_pd.error;
free(working_copy);
return result;
}
result.range.start = parsed_pd.value;
result.range.start_infinity = false;
}
// got the first part now
// advance the pointer
input = separator + 1;
}
// End part or no separator
if (input[0] == '\0') {
// the end is infinite
result.range.end_infinity = true;
} else {
perfdata_value_parser_wrapper parsed_pd = parse_pd_value(input);
if (parsed_pd.error != MP_PARSING_SUCCESS) {
result.error = parsed_pd.error;
return result;
}
result.range.end = parsed_pd.value;
result.range.end_infinity = false;
}
free(working_copy);
return result;
}
double_parser_wrapper parse_double(const char *input) {
double_parser_wrapper result = {
.error = MP_PARSING_SUCCESS,
};
if (input == NULL) {
result.error = MP_PARSING_FAILURE;
return result;
}
char *endptr = NULL;
errno = 0;
double tmp = strtod(input, &endptr);
if (input == endptr) {
// man 3 strtod says, no conversion performed
result.error = MP_PARSING_FAILURE;
return result;
}
if (errno) {
// some other error
// TODO maybe differentiate a little bit
result.error = MP_PARSING_FAILURE;
return result;
}
result.value = mp_create_pd_value(tmp);
return result;
}
integer_parser_wrapper parse_integer(const char *input) {
integer_parser_wrapper result = {
.error = MP_PARSING_SUCCESS,
};
if (input == NULL) {
result.error = MP_PARSING_FAILURE;
return result;
}
char *endptr = NULL;
errno = 0;
long long tmp = strtoll(input, &endptr, 0);
// validating *sigh*
if (*endptr != '\0') {
// something went wrong in strtoll
if (tmp == LLONG_MIN) {
// underflow
result.error = MP_RANGE_PARSING_UNDERFLOW;
return result;
}
if (tmp == LLONG_MAX) {
// overflow
result.error = MP_RANGE_PARSING_OVERFLOW;
return result;
}
// still wrong, but not sure why, probably invalid characters
if (errno == EINVAL) {
result.error = MP_RANGE_PARSING_INVALID_CHAR;
return result;
}
// some other error, do catch all here
result.error = MP_RANGE_PARSING_FAILURE;
return result;
}
// no error, should be fine
result.value = mp_create_pd_value(tmp);
return result;
}
perfdata_value_parser_wrapper parse_pd_value(const char *input) {
// try integer first
integer_parser_wrapper tmp_int = parse_integer(input);
if (tmp_int.error == MP_PARSING_SUCCESS) {
perfdata_value_parser_wrapper result = {
.error = tmp_int.error,
.value = tmp_int.value,
};
return result;
}
double_parser_wrapper tmp_double = parse_double(input);
perfdata_value_parser_wrapper result = {};
if (tmp_double.error == MP_PARSING_SUCCESS) {
result.error = tmp_double.error;
result.value = tmp_double.value;
} else {
result.error = tmp_double.error;
}
return result;
}
mp_perfdata mp_set_pd_max_value(mp_perfdata perfdata, mp_perfdata_value value) {
perfdata.max = value;
perfdata.max_present = true;
return perfdata;
}
mp_perfdata mp_set_pd_min_value(mp_perfdata perfdata, mp_perfdata_value value) {
perfdata.min = value;
perfdata.min_present = true;
return perfdata;
}
double mp_get_pd_value(mp_perfdata_value value) {
assert(value.type != PD_TYPE_NONE);
switch (value.type) {
case PD_TYPE_DOUBLE:
return value.pd_double;
case PD_TYPE_INT:
return (double)value.pd_int;
case PD_TYPE_UINT:
return (double)value.pd_uint;
default:
return 0; // just to make the compiler happy
}
}
mp_perfdata_value mp_pd_value_multiply(mp_perfdata_value left, mp_perfdata_value right) {
if (left.type == right.type) {
switch (left.type) {
case PD_TYPE_DOUBLE:
left.pd_double *= right.pd_double;
return left;
case PD_TYPE_INT:
left.pd_int *= right.pd_int;
return left;
case PD_TYPE_UINT:
left.pd_uint *= right.pd_uint;
return left;
default:
// what to here?
return left;
}
}
// Different types, oh boy, just do the lazy thing for now and switch to double
switch (left.type) {
case PD_TYPE_INT:
left.pd_double = (double)left.pd_int;
left.type = PD_TYPE_DOUBLE;
break;
case PD_TYPE_UINT:
left.pd_double = (double)left.pd_uint;
left.type = PD_TYPE_DOUBLE;
break;
}
switch (right.type) {
case PD_TYPE_INT:
right.pd_double = (double)right.pd_int;
right.type = PD_TYPE_DOUBLE;
break;
case PD_TYPE_UINT:
right.pd_double = (double)right.pd_uint;
right.type = PD_TYPE_DOUBLE;
break;
}
left.pd_double *= right.pd_double;
return left;
}
mp_range mp_range_multiply(mp_range range, mp_perfdata_value factor) {
if (!range.end_infinity) {
range.end = mp_pd_value_multiply(range.end, factor);
}
if (!range.start_infinity) {
range.start = mp_pd_value_multiply(range.start, factor);
}
return range;
}