summaryrefslogtreecommitdiffstats
path: root/lib/output.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/output.c')
-rw-r--r--lib/output.c660
1 files changed, 660 insertions, 0 deletions
diff --git a/lib/output.c b/lib/output.c
new file mode 100644
index 00000000..54d505d9
--- /dev/null
+++ b/lib/output.c
@@ -0,0 +1,660 @@
1#include "./output.h"
2#include "./utils_base.h"
3#include "../plugins/utils.h"
4
5#include <assert.h>
6#include <stdlib.h>
7#include <string.h>
8#include <strings.h>
9// #include <cjson/cJSON.h>
10#include "./vendor/cJSON/cJSON.h"
11#include "perfdata.h"
12#include "states.h"
13
14// == Global variables
15static mp_output_format output_format = MP_FORMAT_DEFAULT;
16static mp_output_detail_level level_of_detail = MP_DETAIL_ALL;
17
18// == Prototypes ==
19static char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check,
20 unsigned int indentation);
21static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck);
22
23// == Implementation ==
24
25/*
26 * Generate output string for a mp_subcheck object
27 */
28static inline char *fmt_subcheck_perfdata(mp_subcheck check) {
29 char *result = strdup("");
30 int added = 0;
31
32 if (check.perfdata != NULL) {
33 added = asprintf(&result, "%s", pd_list_to_string(*check.perfdata));
34 }
35
36 if (check.subchecks == NULL) {
37 // No subchecks, return here
38 return result;
39 }
40
41 mp_subcheck_list *subchecks = check.subchecks;
42
43 while (subchecks != NULL) {
44 if (added > 0) {
45 added = asprintf(&result, "%s %s", result, fmt_subcheck_perfdata(subchecks->subcheck));
46 } else {
47 // TODO free previous result here?
48 added = asprintf(&result, "%s", fmt_subcheck_perfdata(subchecks->subcheck));
49 }
50
51 subchecks = subchecks->next;
52 }
53
54 return result;
55}
56
57/*
58 * Initialiser for a mp_check object. Always use this to get a new one!
59 * It sets useful defaults
60 */
61mp_check mp_check_init(void) {
62 mp_check check = {
63 .evaluation_function = &mp_eval_check_default,
64 .default_output_override = NULL,
65 .default_output_override_content = NULL,
66 };
67 return check;
68}
69
70/*
71 * Initialiser for a mp_subcheck object. Always use this to get a new one!
72 * It sets useful defaults
73 */
74mp_subcheck mp_subcheck_init(void) {
75 mp_subcheck tmp = {0};
76 tmp.default_state = STATE_UNKNOWN; // Default state is unknown
77 tmp.state_set_explicitly = false;
78 return tmp;
79}
80
81/*
82 * Add a subcheck to a (the one and only) check object
83 */
84int mp_add_subcheck_to_check(mp_check check[static 1], mp_subcheck subcheck) {
85 assert(subcheck.output != NULL); // There must be output in a subcheck
86
87 mp_subcheck_list *tmp = NULL;
88
89 if (check->subchecks == NULL) {
90 check->subchecks = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
91 if (check->subchecks == NULL) {
92 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
93 }
94
95 check->subchecks->subcheck = subcheck;
96 check->subchecks->next = NULL;
97 } else {
98 // Search for the end
99 tmp = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
100 if (tmp == NULL) {
101 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
102 }
103
104 tmp->subcheck = subcheck;
105 tmp->next = check->subchecks;
106
107 check->subchecks = tmp;
108 }
109
110 return 0;
111}
112
113/*
114 * Add a mp_perfdata data point to a mp_subcheck object
115 */
116void mp_add_perfdata_to_subcheck(mp_subcheck check[static 1], const mp_perfdata perfData) {
117 if (check->perfdata == NULL) {
118 check->perfdata = pd_list_init();
119 }
120 pd_list_append(check->perfdata, perfData);
121}
122
123/*
124 * Add a mp_subcheck object to another one. The seconde mp_subcheck (argument) is the lower in the
125 * hierarchy
126 */
127int mp_add_subcheck_to_subcheck(mp_subcheck check[static 1], mp_subcheck subcheck) {
128 if (subcheck.output == NULL) {
129 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__,
130 "Sub check output is NULL");
131 }
132
133 mp_subcheck_list *tmp = NULL;
134
135 if (check->subchecks == NULL) {
136 check->subchecks = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
137 if (check->subchecks == NULL) {
138 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
139 }
140
141 tmp = check->subchecks;
142 } else {
143 // Search for the end
144 tmp = check->subchecks;
145
146 while (tmp->next != NULL) {
147 tmp = tmp->next;
148 }
149
150 tmp->next = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
151 if (tmp->next == NULL) {
152 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
153 }
154
155 tmp = tmp->next;
156 }
157
158 tmp->subcheck = subcheck;
159
160 return 0;
161}
162
163/*
164 * Add a manual summary to a mp_check object, effectively replacing
165 * the autogenerated one
166 */
167void mp_add_summary(mp_check check[static 1], char *summary) { check->summary = summary; }
168
169/*
170 * Generate the summary string of a mp_check object based on its subchecks
171 */
172char *get_subcheck_summary(mp_check check) {
173 mp_subcheck_list *subchecks = check.subchecks;
174
175 unsigned int ok = 0;
176 unsigned int warning = 0;
177 unsigned int critical = 0;
178 unsigned int unknown = 0;
179 while (subchecks != NULL) {
180 switch (mp_compute_subcheck_state(subchecks->subcheck)) {
181 case STATE_OK:
182 ok++;
183 break;
184 case STATE_WARNING:
185 warning++;
186 break;
187 case STATE_CRITICAL:
188 critical++;
189 break;
190 case STATE_UNKNOWN:
191 unknown++;
192 break;
193 default:
194 die(STATE_UNKNOWN, "Unknown state in get_subcheck_summary");
195 }
196 subchecks = subchecks->next;
197 }
198 char *result = NULL;
199 asprintf(&result, "ok=%d, warning=%d, critical=%d, unknown=%d", ok, warning, critical, unknown);
200 return result;
201}
202
203mp_state_enum mp_compute_subcheck_state(const mp_subcheck subcheck) {
204 if (subcheck.evaluation_function == NULL) {
205 return mp_eval_subcheck_default(subcheck);
206 }
207 return subcheck.evaluation_function(subcheck);
208}
209
210/*
211 * Generate the result state of a mp_subcheck object based on its own state and its subchecks
212 * states
213 */
214mp_state_enum mp_eval_subcheck_default(mp_subcheck subcheck) {
215 if (subcheck.evaluation_function != NULL) {
216 return subcheck.evaluation_function(subcheck);
217 }
218
219 if (subcheck.state_set_explicitly) {
220 return subcheck.state;
221 }
222
223 mp_subcheck_list *scl = subcheck.subchecks;
224
225 if (scl == NULL) {
226 return subcheck.default_state;
227 }
228
229 mp_state_enum result = STATE_OK;
230
231 while (scl != NULL) {
232 result = max_state_alt(result, mp_compute_subcheck_state(scl->subcheck));
233 scl = scl->next;
234 }
235
236 return result;
237}
238
239mp_state_enum mp_compute_check_state(const mp_check check) {
240 // just a safety check
241 if (check.evaluation_function == NULL) {
242 return mp_eval_check_default(check);
243 }
244 return check.evaluation_function(check);
245}
246
247/*
248 * Generate the result state of a mp_check object based on its own state and its subchecks states
249 */
250mp_state_enum mp_eval_check_default(const mp_check check) {
251 assert(check.subchecks != NULL); // a mp_check without subchecks is invalid, die here
252
253 mp_subcheck_list *scl = check.subchecks;
254 mp_state_enum result = STATE_OK;
255
256 while (scl != NULL) {
257 result = max_state_alt(result, mp_compute_subcheck_state(scl->subcheck));
258 scl = scl->next;
259 }
260
261 return result;
262}
263
264// Remove troublesome symbols from plugin output
265char *sanitize_output_insitu(char *input) {
266 if (input == NULL) {
267 return input;
268 }
269
270 for (char *walker = input; *walker != '\0'; walker++) {
271 if (*walker == '|') {
272 *walker = ' ';
273 }
274 }
275
276 return input;
277}
278
279/*
280 * Generate output string for a mp_check object
281 * Non static to be available for testing functions
282 */
283char *mp_fmt_output(mp_check check) {
284 char *result = NULL;
285
286 switch (output_format) {
287 case MP_FORMAT_MULTI_LINE: {
288 if (check.default_output_override != NULL) {
289 result = check.default_output_override(check.default_output_override_content);
290 break;
291 }
292
293 if (check.summary == NULL) {
294 check.summary = get_subcheck_summary(check);
295 }
296
297 asprintf(&result, "[%s] - %s", state_text(mp_compute_check_state(check)), check.summary);
298
299 mp_subcheck_list *subchecks = check.subchecks;
300
301 while (subchecks != NULL) {
302 if (level_of_detail == MP_DETAIL_ALL ||
303 mp_compute_subcheck_state(subchecks->subcheck) != STATE_OK) {
304 asprintf(&result, "%s\n%s", result,
305 fmt_subcheck_output(MP_FORMAT_MULTI_LINE, subchecks->subcheck, 1));
306 }
307 subchecks = subchecks->next;
308 }
309
310 char *pd_string = NULL;
311 subchecks = check.subchecks;
312
313 while (subchecks != NULL) {
314 if (pd_string == NULL) {
315 asprintf(&pd_string, "%s", fmt_subcheck_perfdata(subchecks->subcheck));
316 } else {
317 asprintf(&pd_string, "%s %s", pd_string,
318 fmt_subcheck_perfdata(subchecks->subcheck));
319 }
320
321 subchecks = subchecks->next;
322 }
323
324 result = sanitize_output_insitu(result);
325
326 if (pd_string != NULL && strlen(pd_string) > 0) {
327 asprintf(&result, "%s|%s", result, pd_string);
328 }
329
330 break;
331 }
332 case MP_FORMAT_TEST_JSON: {
333 cJSON *resultObject = cJSON_CreateObject();
334 if (resultObject == NULL) {
335 die(STATE_UNKNOWN, "cJSON_CreateObject failed");
336 }
337
338 cJSON *resultState = cJSON_CreateString(state_text(mp_compute_check_state(check)));
339 cJSON_AddItemToObject(resultObject, "state", resultState);
340
341 if (check.summary == NULL) {
342 check.summary = get_subcheck_summary(check);
343 }
344
345 cJSON *summary = cJSON_CreateString(check.summary);
346 cJSON_AddItemToObject(resultObject, "summary", summary);
347
348 if (check.subchecks != NULL) {
349 cJSON *subchecks = cJSON_CreateArray();
350
351 mp_subcheck_list *sc = check.subchecks;
352
353 while (sc != NULL) {
354 cJSON *sc_json = json_serialize_subcheck(sc->subcheck);
355 cJSON_AddItemToArray(subchecks, sc_json);
356 sc = sc->next;
357 }
358
359 cJSON_AddItemToObject(resultObject, "checks", subchecks);
360 }
361
362 result = cJSON_PrintUnformatted(resultObject);
363 break;
364 }
365 default:
366 die(STATE_UNKNOWN, "Invalid format");
367 }
368
369 return result;
370}
371
372/*
373 * Helper function to properly indent the output lines when using multiline
374 * formats
375 */
376static char *generate_indentation_string(unsigned int indentation) {
377 char *result = calloc(indentation + 1, sizeof(char));
378
379 for (unsigned int i = 0; i < indentation; i++) {
380 result[i] = '\t';
381 }
382
383 return result;
384}
385
386/*
387 * Helper function to generate the output string of mp_subcheck
388 */
389static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check,
390 unsigned int indentation) {
391 char *result = NULL;
392 mp_subcheck_list *subchecks = NULL;
393
394 switch (output_format) {
395 case MP_FORMAT_MULTI_LINE: {
396 char *tmp_string = NULL;
397 if ((tmp_string = strchr(check.output, '\n')) != NULL) {
398 // This is a multiline string, put the correct indentation in before proceeding
399 char *intermediate_string = "";
400 bool have_residual_chars = false;
401
402 while (tmp_string != NULL) {
403 *tmp_string = '\0';
404 asprintf(&intermediate_string, "%s%s\n%s", intermediate_string, check.output,
405 generate_indentation_string(
406 indentation + 1)); // one more indentation to make it look better
407
408 if (*(tmp_string + 1) != '\0') {
409 check.output = tmp_string + 1;
410 have_residual_chars = true;
411 } else {
412 // Null after the \n, so this is the end
413 have_residual_chars = false;
414 break;
415 }
416
417 tmp_string = strchr(check.output, '\n');
418 }
419
420 // add the rest (if any)
421 if (have_residual_chars) {
422 char *tmp = check.output;
423 xasprintf(&check.output, "%s\n%s%s", intermediate_string,
424 generate_indentation_string(indentation + 1), tmp);
425 } else {
426 check.output = intermediate_string;
427 }
428 }
429 asprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation),
430 state_text(mp_compute_subcheck_state(check)), check.output);
431
432 subchecks = check.subchecks;
433
434 while (subchecks != NULL) {
435 asprintf(&result, "%s\n%s", result,
436 fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1));
437 subchecks = subchecks->next;
438 }
439 return result;
440 }
441 default:
442 die(STATE_UNKNOWN, "Invalid format");
443 }
444}
445
446static inline cJSON *json_serialise_pd_value(mp_perfdata_value value) {
447 cJSON *result = cJSON_CreateObject();
448
449 switch (value.type) {
450 case PD_TYPE_DOUBLE:
451 cJSON_AddStringToObject(result, "type", "double");
452 break;
453 case PD_TYPE_INT:
454 cJSON_AddStringToObject(result, "type", "int");
455 break;
456 case PD_TYPE_UINT:
457 cJSON_AddStringToObject(result, "type", "uint");
458 break;
459 case PD_TYPE_NONE:
460 die(STATE_UNKNOWN, "Perfdata type was None in json_serialise_pd_value");
461 }
462 cJSON_AddStringToObject(result, "value", pd_value_to_string(value));
463
464 return result;
465}
466
467static inline cJSON *json_serialise_range(mp_range range) {
468 cJSON *result = cJSON_CreateObject();
469
470 if (range.alert_on_inside_range) {
471 cJSON_AddBoolToObject(result, "alert_on_inside", true);
472 } else {
473 cJSON_AddBoolToObject(result, "alert_on_inside", false);
474 }
475
476 if (range.end_infinity) {
477 cJSON_AddStringToObject(result, "end", "inf");
478 } else {
479 cJSON_AddItemToObject(result, "end", json_serialise_pd_value(range.end));
480 }
481
482 if (range.start_infinity) {
483 cJSON_AddStringToObject(result, "start", "inf");
484 } else {
485 cJSON_AddItemToObject(result, "start", json_serialise_pd_value(range.end));
486 }
487
488 return result;
489}
490
491static inline cJSON *json_serialise_pd(mp_perfdata pd_val) {
492 cJSON *result = cJSON_CreateObject();
493
494 // Label
495 cJSON_AddStringToObject(result, "label", pd_val.label);
496
497 // Value
498 cJSON_AddItemToObject(result, "value", json_serialise_pd_value(pd_val.value));
499
500 // Uom
501 cJSON_AddStringToObject(result, "uom", pd_val.uom);
502
503 // Warn/Crit
504 if (pd_val.warn_present) {
505 cJSON *warn = json_serialise_range(pd_val.warn);
506 cJSON_AddItemToObject(result, "warn", warn);
507 }
508 if (pd_val.crit_present) {
509 cJSON *crit = json_serialise_range(pd_val.crit);
510 cJSON_AddItemToObject(result, "crit", crit);
511 }
512
513 if (pd_val.min_present) {
514 cJSON_AddItemToObject(result, "min", json_serialise_pd_value(pd_val.min));
515 }
516 if (pd_val.max_present) {
517 cJSON_AddItemToObject(result, "max", json_serialise_pd_value(pd_val.max));
518 }
519
520 return result;
521}
522
523static inline cJSON *json_serialise_pd_list(pd_list *list) {
524 cJSON *result = cJSON_CreateArray();
525
526 do {
527 cJSON *pd_value = json_serialise_pd(list->data);
528 cJSON_AddItemToArray(result, pd_value);
529 list = list->next;
530 } while (list != NULL);
531
532 return result;
533}
534
535static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck) {
536 cJSON *result = cJSON_CreateObject();
537
538 // Human readable output
539 cJSON *output = cJSON_CreateString(subcheck.output);
540 cJSON_AddItemToObject(result, "output", output);
541
542 // Test state (aka Exit Code)
543 cJSON *state = cJSON_CreateString(state_text(mp_compute_subcheck_state(subcheck)));
544 cJSON_AddItemToObject(result, "state", state);
545
546 // Perfdata
547 if (subcheck.perfdata != NULL) {
548 cJSON *perfdata = json_serialise_pd_list(subcheck.perfdata);
549 cJSON_AddItemToObject(result, "perfdata", perfdata);
550 }
551
552 if (subcheck.subchecks != NULL) {
553 cJSON *subchecks = cJSON_CreateArray();
554
555 mp_subcheck_list *sc = subcheck.subchecks;
556
557 while (sc != NULL) {
558 cJSON *sc_json = json_serialize_subcheck(sc->subcheck);
559 cJSON_AddItemToArray(subchecks, sc_json);
560 sc = sc->next;
561 }
562
563 cJSON_AddItemToObject(result, "checks", subchecks);
564 }
565
566 return result;
567}
568
569/*
570 * Wrapper function to print the output string of a mp_check object
571 * Use this in concrete plugins.
572 */
573void mp_print_output(mp_check check) { puts(mp_fmt_output(check)); }
574
575/*
576 * Convenience function to print the output string of a mp_check object and exit
577 * the program with the resulting state.
578 * Intended to be used to exit a monitoring plugin.
579 */
580void mp_exit(mp_check check) {
581 mp_print_output(check);
582 if (output_format == MP_FORMAT_TEST_JSON) {
583 exit(0);
584 }
585
586 exit(mp_compute_check_state(check));
587}
588
589/*
590 * Function to set the result state of a mp_subcheck object explicitly.
591 * This will overwrite the default state AND states derived from it's subchecks
592 */
593mp_subcheck mp_set_subcheck_state(mp_subcheck check, mp_state_enum state) {
594 check.state = state;
595 check.state_set_explicitly = true;
596 return check;
597}
598
599/*
600 * Function to set the default result state of a mp_subcheck object. This state
601 * will be used if neither an explicit state is set (see *mp_set_subcheck_state*)
602 * nor does it include other subchecks
603 */
604mp_subcheck mp_set_subcheck_default_state(mp_subcheck check, mp_state_enum state) {
605 check.default_state = state;
606 return check;
607}
608
609char *mp_output_format_map[] = {
610 [MP_FORMAT_MULTI_LINE] = "multi-line",
611 [MP_FORMAT_TEST_JSON] = "mp-test-json",
612};
613
614/*
615 * Function to parse the output from a string
616 */
617parsed_output_format mp_parse_output_format(char *format_string) {
618 parsed_output_format result = {
619 .parsing_success = false,
620 .output_format = MP_FORMAT_DEFAULT,
621 };
622
623 for (mp_output_format i = 0; i < (sizeof(mp_output_format_map) / sizeof(char *)); i++) {
624 if (strcasecmp(mp_output_format_map[i], format_string) == 0) {
625 result.parsing_success = true;
626 result.output_format = i;
627 break;
628 }
629 }
630
631 return result;
632}
633
634void mp_set_format(mp_output_format format) { output_format = format; }
635
636mp_output_format mp_get_format(void) { return output_format; }
637
638void mp_set_level_of_detail(mp_output_detail_level level) { level_of_detail = level; }
639
640mp_output_detail_level mp_get_level_of_detail(void) { return level_of_detail; }
641
642mp_state_enum mp_eval_ok(mp_check overall) {
643 (void)overall;
644 return STATE_OK;
645}
646
647mp_state_enum mp_eval_warning(mp_check overall) {
648 (void)overall;
649 return STATE_WARNING;
650}
651
652mp_state_enum mp_eval_critical(mp_check overall) {
653 (void)overall;
654 return STATE_CRITICAL;
655}
656
657mp_state_enum mp_eval_unknown(mp_check overall) {
658 (void)overall;
659 return STATE_UNKNOWN;
660}