monitoring-plugins/lib/output.c
Lorenz Kästle 7ab5b3ba34
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Spellcheck / codespell (push) Has been cancelled
Tests / Running unit and integrationt tests (push) Has been cancelled
Tests / Running rpm build test on almalinux:9 (push) Has been cancelled
Tests / Running rpm build test on fedora:latest (push) Has been cancelled
Tests / Running rpm build test on rockylinux:8 (push) Has been cancelled
Tests Debian:Testing and Fedora:Rawhide / Running unit and integrationt tests (push) Has been cancelled
Tests Debian:Testing and Fedora:Rawhide / Running rpm build test on fedora:rawhide (push) Has been cancelled
lib: properly separate perfdata from different sub subchecks (#2197)
Previously there was a space missing between perfdata from differen sub subchecks which irritated my monitoring system and caused it to interpet two data points as one.
This puts the space back in there.
2025-12-04 15:31:22 +01:00

636 lines
16 KiB
C

#include "./output.h"
#include "./utils_base.h"
#include "../plugins/utils.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
// #include <cjson/cJSON.h>
#include "./vendor/cJSON/cJSON.h"
#include "perfdata.h"
#include "states.h"
// == Global variables
static mp_output_format output_format = MP_FORMAT_DEFAULT;
static mp_output_detail_level level_of_detail = MP_DETAIL_ALL;
// == Prototypes ==
static char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check,
unsigned int indentation);
static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck);
// == Implementation ==
/*
* Generate output string for a mp_subcheck object
*/
static inline char *fmt_subcheck_perfdata(mp_subcheck check) {
char *result = strdup("");
int added = 0;
if (check.perfdata != NULL) {
added = asprintf(&result, "%s", pd_list_to_string(*check.perfdata));
}
if (check.subchecks == NULL) {
// No subchecks, return here
return result;
}
mp_subcheck_list *subchecks = check.subchecks;
while (subchecks != NULL) {
if (added > 0) {
added = asprintf(&result, "%s %s", result, fmt_subcheck_perfdata(subchecks->subcheck));
} else {
// TODO free previous result here?
added = asprintf(&result, "%s", fmt_subcheck_perfdata(subchecks->subcheck));
}
subchecks = subchecks->next;
}
return result;
}
/*
* Initialiser for a mp_check object. Always use this to get a new one!
* It sets useful defaults
*/
mp_check mp_check_init(void) {
mp_check check = {
.evaluation_function = &mp_eval_check_default,
};
return check;
}
/*
* Initialiser for a mp_subcheck object. Always use this to get a new one!
* It sets useful defaults
*/
mp_subcheck mp_subcheck_init(void) {
mp_subcheck tmp = {0};
tmp.default_state = STATE_UNKNOWN; // Default state is unknown
tmp.state_set_explicitly = false;
return tmp;
}
/*
* Add a subcheck to a (the one and only) check object
*/
int mp_add_subcheck_to_check(mp_check check[static 1], mp_subcheck subcheck) {
assert(subcheck.output != NULL); // There must be output in a subcheck
mp_subcheck_list *tmp = NULL;
if (check->subchecks == NULL) {
check->subchecks = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
if (check->subchecks == NULL) {
die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
}
check->subchecks->subcheck = subcheck;
check->subchecks->next = NULL;
} else {
// Search for the end
tmp = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
if (tmp == NULL) {
die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
}
tmp->subcheck = subcheck;
tmp->next = check->subchecks;
check->subchecks = tmp;
}
return 0;
}
/*
* Add a mp_perfdata data point to a mp_subcheck object
*/
void mp_add_perfdata_to_subcheck(mp_subcheck check[static 1], const mp_perfdata perfData) {
if (check->perfdata == NULL) {
check->perfdata = pd_list_init();
}
pd_list_append(check->perfdata, perfData);
}
/*
* Add a mp_subcheck object to another one. The seconde mp_subcheck (argument) is the lower in the
* hierarchy
*/
int mp_add_subcheck_to_subcheck(mp_subcheck check[static 1], mp_subcheck subcheck) {
if (subcheck.output == NULL) {
die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__,
"Sub check output is NULL");
}
mp_subcheck_list *tmp = NULL;
if (check->subchecks == NULL) {
check->subchecks = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
if (check->subchecks == NULL) {
die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
}
tmp = check->subchecks;
} else {
// Search for the end
tmp = check->subchecks;
while (tmp->next != NULL) {
tmp = tmp->next;
}
tmp->next = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
if (tmp->next == NULL) {
die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
}
tmp = tmp->next;
}
tmp->subcheck = subcheck;
return 0;
}
/*
* Add a manual summary to a mp_check object, effectively replacing
* the autogenerated one
*/
void mp_add_summary(mp_check check[static 1], char *summary) { check->summary = summary; }
/*
* Generate the summary string of a mp_check object based on it's subchecks
*/
char *get_subcheck_summary(mp_check check) {
mp_subcheck_list *subchecks = check.subchecks;
unsigned int ok = 0;
unsigned int warning = 0;
unsigned int critical = 0;
unsigned int unknown = 0;
while (subchecks != NULL) {
switch (subchecks->subcheck.state) {
case STATE_OK:
ok++;
break;
case STATE_WARNING:
warning++;
break;
case STATE_CRITICAL:
critical++;
break;
case STATE_UNKNOWN:
unknown++;
break;
default:
die(STATE_UNKNOWN, "Unknown state in get_subcheck_summary");
}
subchecks = subchecks->next;
}
char *result = NULL;
asprintf(&result, "ok=%d, warning=%d, critical=%d, unknown=%d", ok, warning, critical, unknown);
return result;
}
mp_state_enum mp_compute_subcheck_state(const mp_subcheck subcheck) {
if (subcheck.evaluation_function == NULL) {
return mp_eval_subcheck_default(subcheck);
}
return subcheck.evaluation_function(subcheck);
}
/*
* Generate the result state of a mp_subcheck object based on its own state and its subchecks
* states
*/
mp_state_enum mp_eval_subcheck_default(mp_subcheck subcheck) {
if (subcheck.evaluation_function != NULL) {
return subcheck.evaluation_function(subcheck);
}
if (subcheck.state_set_explicitly) {
return subcheck.state;
}
mp_subcheck_list *scl = subcheck.subchecks;
if (scl == NULL) {
return subcheck.default_state;
}
mp_state_enum result = STATE_OK;
while (scl != NULL) {
result = max_state_alt(result, mp_compute_subcheck_state(scl->subcheck));
scl = scl->next;
}
return result;
}
mp_state_enum mp_compute_check_state(const mp_check check) {
// just a safety check
if (check.evaluation_function == NULL) {
return mp_eval_check_default(check);
}
return check.evaluation_function(check);
}
/*
* Generate the result state of a mp_check object based on it's own state and it's subchecks states
*/
mp_state_enum mp_eval_check_default(const mp_check check) {
assert(check.subchecks != NULL); // a mp_check without subchecks is invalid, die here
mp_subcheck_list *scl = check.subchecks;
mp_state_enum result = STATE_OK;
while (scl != NULL) {
result = max_state_alt(result, mp_compute_subcheck_state(scl->subcheck));
scl = scl->next;
}
return result;
}
/*
* Generate output string for a mp_check object
* Non static to be available for testing functions
*/
char *mp_fmt_output(mp_check check) {
char *result = NULL;
switch (output_format) {
case MP_FORMAT_MULTI_LINE: {
if (check.summary == NULL) {
check.summary = get_subcheck_summary(check);
}
asprintf(&result, "[%s] - %s", state_text(mp_compute_check_state(check)), check.summary);
mp_subcheck_list *subchecks = check.subchecks;
while (subchecks != NULL) {
if (level_of_detail == MP_DETAIL_ALL ||
mp_compute_subcheck_state(subchecks->subcheck) != STATE_OK) {
asprintf(&result, "%s\n%s", result,
fmt_subcheck_output(MP_FORMAT_MULTI_LINE, subchecks->subcheck, 1));
}
subchecks = subchecks->next;
}
char *pd_string = NULL;
subchecks = check.subchecks;
while (subchecks != NULL) {
if (pd_string == NULL) {
asprintf(&pd_string, "%s", fmt_subcheck_perfdata(subchecks->subcheck));
} else {
asprintf(&pd_string, "%s %s", pd_string,
fmt_subcheck_perfdata(subchecks->subcheck));
}
subchecks = subchecks->next;
}
if (pd_string != NULL && strlen(pd_string) > 0) {
asprintf(&result, "%s|%s", result, pd_string);
}
break;
}
case MP_FORMAT_TEST_JSON: {
cJSON *resultObject = cJSON_CreateObject();
if (resultObject == NULL) {
die(STATE_UNKNOWN, "cJSON_CreateObject failed");
}
cJSON *resultState = cJSON_CreateString(state_text(mp_compute_check_state(check)));
cJSON_AddItemToObject(resultObject, "state", resultState);
if (check.summary == NULL) {
check.summary = get_subcheck_summary(check);
}
cJSON *summary = cJSON_CreateString(check.summary);
cJSON_AddItemToObject(resultObject, "summary", summary);
if (check.subchecks != NULL) {
cJSON *subchecks = cJSON_CreateArray();
mp_subcheck_list *sc = check.subchecks;
while (sc != NULL) {
cJSON *sc_json = json_serialize_subcheck(sc->subcheck);
cJSON_AddItemToArray(subchecks, sc_json);
sc = sc->next;
}
cJSON_AddItemToObject(resultObject, "checks", subchecks);
}
result = cJSON_PrintUnformatted(resultObject);
break;
}
default:
die(STATE_UNKNOWN, "Invalid format");
}
return result;
}
/*
* Helper function to properly indent the output lines when using multiline
* formats
*/
static char *generate_indentation_string(unsigned int indentation) {
char *result = calloc(indentation + 1, sizeof(char));
for (unsigned int i = 0; i < indentation; i++) {
result[i] = '\t';
}
return result;
}
/*
* Helper function to generate the output string of mp_subcheck
*/
static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check,
unsigned int indentation) {
char *result = NULL;
mp_subcheck_list *subchecks = NULL;
switch (output_format) {
case MP_FORMAT_MULTI_LINE: {
char *tmp_string = NULL;
if ((tmp_string = strchr(check.output, '\n')) != NULL) {
// This is a multiline string, put the correct indentation in before proceeding
char *intermediate_string = "";
bool have_residual_chars = false;
while (tmp_string != NULL) {
*tmp_string = '\0';
asprintf(&intermediate_string, "%s%s\n%s", intermediate_string, check.output,
generate_indentation_string(
indentation + 1)); // one more indentation to make it look better
if (*(tmp_string + 1) != '\0') {
check.output = tmp_string + 1;
have_residual_chars = true;
} else {
// Null after the \n, so this is the end
have_residual_chars = false;
break;
}
tmp_string = strchr(check.output, '\n');
}
// add the rest (if any)
if (have_residual_chars) {
char *tmp = check.output;
xasprintf(&check.output, "%s\n%s%s", intermediate_string,
generate_indentation_string(indentation + 1), tmp);
} else {
check.output = intermediate_string;
}
}
asprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation),
state_text(mp_compute_subcheck_state(check)), check.output);
subchecks = check.subchecks;
while (subchecks != NULL) {
asprintf(&result, "%s\n%s", result,
fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1));
subchecks = subchecks->next;
}
return result;
}
default:
die(STATE_UNKNOWN, "Invalid format");
}
}
static inline cJSON *json_serialise_pd_value(mp_perfdata_value value) {
cJSON *result = cJSON_CreateObject();
switch (value.type) {
case PD_TYPE_DOUBLE:
cJSON_AddStringToObject(result, "type", "double");
break;
case PD_TYPE_INT:
cJSON_AddStringToObject(result, "type", "int");
break;
case PD_TYPE_UINT:
cJSON_AddStringToObject(result, "type", "uint");
break;
case PD_TYPE_NONE:
die(STATE_UNKNOWN, "Perfdata type was None in json_serialise_pd_value");
}
cJSON_AddStringToObject(result, "value", pd_value_to_string(value));
return result;
}
static inline cJSON *json_serialise_range(mp_range range) {
cJSON *result = cJSON_CreateObject();
if (range.alert_on_inside_range) {
cJSON_AddBoolToObject(result, "alert_on_inside", true);
} else {
cJSON_AddBoolToObject(result, "alert_on_inside", false);
}
if (range.end_infinity) {
cJSON_AddStringToObject(result, "end", "inf");
} else {
cJSON_AddItemToObject(result, "end", json_serialise_pd_value(range.end));
}
if (range.start_infinity) {
cJSON_AddStringToObject(result, "start", "inf");
} else {
cJSON_AddItemToObject(result, "start", json_serialise_pd_value(range.end));
}
return result;
}
static inline cJSON *json_serialise_pd(mp_perfdata pd_val) {
cJSON *result = cJSON_CreateObject();
// Label
cJSON_AddStringToObject(result, "label", pd_val.label);
// Value
cJSON_AddItemToObject(result, "value", json_serialise_pd_value(pd_val.value));
// Uom
cJSON_AddStringToObject(result, "uom", pd_val.uom);
// Warn/Crit
if (pd_val.warn_present) {
cJSON *warn = json_serialise_range(pd_val.warn);
cJSON_AddItemToObject(result, "warn", warn);
}
if (pd_val.crit_present) {
cJSON *crit = json_serialise_range(pd_val.crit);
cJSON_AddItemToObject(result, "crit", crit);
}
if (pd_val.min_present) {
cJSON_AddItemToObject(result, "min", json_serialise_pd_value(pd_val.min));
}
if (pd_val.max_present) {
cJSON_AddItemToObject(result, "max", json_serialise_pd_value(pd_val.max));
}
return result;
}
static inline cJSON *json_serialise_pd_list(pd_list *list) {
cJSON *result = cJSON_CreateArray();
do {
cJSON *pd_value = json_serialise_pd(list->data);
cJSON_AddItemToArray(result, pd_value);
list = list->next;
} while (list != NULL);
return result;
}
static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck) {
cJSON *result = cJSON_CreateObject();
// Human readable output
cJSON *output = cJSON_CreateString(subcheck.output);
cJSON_AddItemToObject(result, "output", output);
// Test state (aka Exit Code)
cJSON *state = cJSON_CreateString(state_text(mp_compute_subcheck_state(subcheck)));
cJSON_AddItemToObject(result, "state", state);
// Perfdata
if (subcheck.perfdata != NULL) {
cJSON *perfdata = json_serialise_pd_list(subcheck.perfdata);
cJSON_AddItemToObject(result, "perfdata", perfdata);
}
if (subcheck.subchecks != NULL) {
cJSON *subchecks = cJSON_CreateArray();
mp_subcheck_list *sc = subcheck.subchecks;
while (sc != NULL) {
cJSON *sc_json = json_serialize_subcheck(sc->subcheck);
cJSON_AddItemToArray(subchecks, sc_json);
sc = sc->next;
}
cJSON_AddItemToObject(result, "checks", subchecks);
}
return result;
}
/*
* Wrapper function to print the output string of a mp_check object
* Use this in concrete plugins.
*/
void mp_print_output(mp_check check) { puts(mp_fmt_output(check)); }
/*
* Convenience function to print the output string of a mp_check object and exit
* the program with the resulting state.
* Intended to be used to exit a monitoring plugin.
*/
void mp_exit(mp_check check) {
mp_print_output(check);
if (output_format == MP_FORMAT_TEST_JSON) {
exit(0);
}
exit(mp_compute_check_state(check));
}
/*
* Function to set the result state of a mp_subcheck object explicitly.
* This will overwrite the default state AND states derived from it's subchecks
*/
mp_subcheck mp_set_subcheck_state(mp_subcheck check, mp_state_enum state) {
check.state = state;
check.state_set_explicitly = true;
return check;
}
/*
* Function to set the default result state of a mp_subcheck object. This state
* will be used if neither an explicit state is set (see *mp_set_subcheck_state*)
* nor does it include other subchecks
*/
mp_subcheck mp_set_subcheck_default_state(mp_subcheck check, mp_state_enum state) {
check.default_state = state;
return check;
}
char *mp_output_format_map[] = {
[MP_FORMAT_MULTI_LINE] = "multi-line",
[MP_FORMAT_TEST_JSON] = "mp-test-json",
};
/*
* Function to parse the output from a string
*/
parsed_output_format mp_parse_output_format(char *format_string) {
parsed_output_format result = {
.parsing_success = false,
.output_format = MP_FORMAT_DEFAULT,
};
for (mp_output_format i = 0; i < (sizeof(mp_output_format_map) / sizeof(char *)); i++) {
if (strcasecmp(mp_output_format_map[i], format_string) == 0) {
result.parsing_success = true;
result.output_format = i;
break;
}
}
return result;
}
void mp_set_format(mp_output_format format) { output_format = format; }
mp_output_format mp_get_format(void) { return output_format; }
void mp_set_level_of_detail(mp_output_detail_level level) { level_of_detail = level; }
mp_output_detail_level mp_get_level_of_detail(void) { return level_of_detail; }
mp_state_enum mp_eval_ok(mp_check overall) {
(void)overall;
return STATE_OK;
}
mp_state_enum mp_eval_warning(mp_check overall) {
(void)overall;
return STATE_WARNING;
}
mp_state_enum mp_eval_critical(mp_check overall) {
(void)overall;
return STATE_CRITICAL;
}
mp_state_enum mp_eval_unknown(mp_check overall) {
(void)overall;
return STATE_UNKNOWN;
}