monitoring-plugins/lib/output.c
2026-01-14 17:28:51 +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 (mp_compute_subcheck_state(subchecks->subcheck)) {
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;
}