From 0645c9fc2c7f801ba3c7d68a17c137a63ada299f Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:58:34 +0100 Subject: Implement new output functionality --- lib/output.c | 464 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 lib/output.c (limited to 'lib/output.c') diff --git a/lib/output.c b/lib/output.c new file mode 100644 index 00000000..9ba049e2 --- /dev/null +++ b/lib/output.c @@ -0,0 +1,464 @@ +#include "./output.h" +#include "./utils_base.h" +#include "../plugins/utils.h" + +#include +#include +#include +#include +// #include +#include "./vendor/cJSON/cJSON.h" + +// == 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 = xasprintf(&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 = xasprintf(&result, "%s%s", result, fmt_subcheck_perfdata(subchecks->subcheck)); + } else { + // TODO free previous result here? + added = xasprintf(&result, "%s", result, 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 = {0}; + check.format = MP_FORMAT_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; + xasprintf(&result, "ok=%d, warning=%d, critical=%d, unknown=%d", ok, warning, critical, unknown); + return result; +} + +/* + * Generate the result state of a mp_subcheck object based on it's own state and it's subchecks states + */ +mp_state_enum mp_compute_subcheck_state(const mp_subcheck check) { + if (check.state_set_explicitly) { + return check.state; + } + + mp_subcheck_list *scl = check.subchecks; + mp_state_enum result = check.default_state; + + while (scl != NULL) { + result = max_state_alt(result, mp_compute_subcheck_state(scl->subcheck)); + scl = scl->next; + } + + return result; +} + +/* + * Generate the result state of a mp_check object based on it's own state and it's subchecks states + */ +mp_state_enum mp_compute_check_state(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 (check.format) { + case MP_FORMAT_SUMMARY_ONLY: + if (check.summary == NULL) { + check.summary = get_subcheck_summary(check); + } + + xasprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); + return result; + + case MP_FORMAT_ONE_LINE: { + /* SERVICE STATUS: First line of output | First part of performance data + * Any number of subsequent lines of output, but note that buffers + * may have a limited size | Second part of performance data, which + * may have continuation lines, too + */ + if (check.summary == NULL) { + check.summary = get_subcheck_summary(check); + } + + xasprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); + + mp_subcheck_list *subchecks = check.subchecks; + + while (subchecks != NULL) { + xasprintf(&result, "%s - %s", result, fmt_subcheck_output(MP_FORMAT_ONE_LINE, subchecks->subcheck, 1)); + subchecks = subchecks->next; + } + + break; + } + case MP_FORMAT_ICINGA_WEB_2: { + if (check.summary == NULL) { + check.summary = get_subcheck_summary(check); + } + + xasprintf(&result, "[%s] - %s", state_text(mp_compute_check_state(check)), check.summary); + + mp_subcheck_list *subchecks = check.subchecks; + + while (subchecks != NULL) { + xasprintf(&result, "%s\n%s", result, fmt_subcheck_output(MP_FORMAT_ICINGA_WEB_2, subchecks->subcheck, 1)); + subchecks = subchecks->next; + } + + char *pd_string = NULL; + subchecks = check.subchecks; + + while (subchecks != NULL) { + if (pd_string == NULL) { + xasprintf(&pd_string, "%s", fmt_subcheck_perfdata(subchecks->subcheck)); + } else { + xasprintf(&pd_string, "%s %s", pd_string, fmt_subcheck_perfdata(subchecks->subcheck)); + } + + subchecks = subchecks->next; + } + + if (pd_string != NULL && strlen(pd_string) > 0) { + xasprintf(&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; + + switch (output_format) { + case MP_FORMAT_ICINGA_WEB_2: + xasprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation), state_text(mp_compute_subcheck_state(check)), + check.output); + + mp_subcheck_list *subchecks = check.subchecks; + + while (subchecks != NULL) { + xasprintf(&result, "%s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1)); + subchecks = subchecks->next; + } + return result; + case MP_FORMAT_ONE_LINE: + return result; + case MP_FORMAT_SUMMARY_ONLY: + return result; + default: + die(STATE_UNKNOWN, "Invalid format"); + } +} + +static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck) { + cJSON *result = cJSON_CreateObject(); + cJSON *output = cJSON_CreateString(subcheck.output); + cJSON_AddItemToObject(result, "output", output); + cJSON *state = cJSON_CreateString(state_text(mp_compute_subcheck_state(subcheck))); + cJSON_AddItemToObject(result, "state", state); + + 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); + 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_ONE_LINE] = "one-line", + [MP_FORMAT_ICINGA_WEB_2] = "icingaweb2", + [MP_FORMAT_SUMMARY_ONLY] = "summary-only", + [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; +} -- cgit v1.2.3-74-g34f1