mirror of
https://github.com/monitoring-plugins/monitoring-plugins.git
synced 2026-02-03 18:49:29 -05:00
636 lines
16 KiB
C
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;
|
|
}
|