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