summaryrefslogtreecommitdiffstats
path: root/lib/output.c
diff options
context:
space:
mode:
authorLorenz Kästle <12514511+RincewindsHat@users.noreply.github.com>2025-02-18 21:58:34 +0100
committerLorenz Kästle <12514511+RincewindsHat@users.noreply.github.com>2025-02-18 21:58:34 +0100
commit0645c9fc2c7f801ba3c7d68a17c137a63ada299f (patch)
treece222906f546f03301defba9ce81ba57591a8eb3 /lib/output.c
parent39680498ee0987a5e0eb203a2c0539aa1fa94d39 (diff)
downloadmonitoring-plugins-0645c9fc2c7f801ba3c7d68a17c137a63ada299f.tar.gz
Implement new output functionality
Diffstat (limited to 'lib/output.c')
-rw-r--r--lib/output.c464
1 files changed, 464 insertions, 0 deletions
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 @@
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
12// == Prototypes ==
13static char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check, unsigned int indentation);
14static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck);
15
16// == Implementation ==
17
18/*
19 * Generate output string for a mp_subcheck object
20 */
21static inline char *fmt_subcheck_perfdata(mp_subcheck check) {
22 char *result = strdup("");
23 int added = 0;
24
25 if (check.perfdata != NULL) {
26 added = xasprintf(&result, "%s", pd_list_to_string(*check.perfdata));
27 }
28
29 if (check.subchecks == NULL) {
30 // No subchecks, return here
31 return result;
32 }
33
34 mp_subcheck_list *subchecks = check.subchecks;
35
36 while (subchecks != NULL) {
37 if (added > 0) {
38 added = xasprintf(&result, "%s%s", result, fmt_subcheck_perfdata(subchecks->subcheck));
39 } else {
40 // TODO free previous result here?
41 added = xasprintf(&result, "%s", result, fmt_subcheck_perfdata(subchecks->subcheck));
42 }
43
44 subchecks = subchecks->next;
45 }
46
47 return result;
48}
49
50/*
51 * Initialiser for a mp_check object. Always use this to get a new one!
52 * It sets useful defaults
53 */
54mp_check mp_check_init(void) {
55 mp_check check = {0};
56 check.format = MP_FORMAT_DEFAULT;
57 return check;
58}
59
60/*
61 * Initialiser for a mp_subcheck object. Always use this to get a new one!
62 * It sets useful defaults
63 */
64mp_subcheck mp_subcheck_init(void) {
65 mp_subcheck tmp = {0};
66 tmp.default_state = STATE_UNKNOWN; // Default state is unknown
67 tmp.state_set_explicitly = false;
68 return tmp;
69}
70
71/*
72 * Add a subcheck to a (the one and only) check object
73 */
74int mp_add_subcheck_to_check(mp_check check[static 1], mp_subcheck subcheck) {
75 assert(subcheck.output != NULL); // There must be output in a subcheck
76
77 mp_subcheck_list *tmp = NULL;
78
79 if (check->subchecks == NULL) {
80 check->subchecks = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
81 if (check->subchecks == NULL) {
82 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
83 }
84
85 check->subchecks->subcheck = subcheck;
86 check->subchecks->next = NULL;
87 } else {
88 // Search for the end
89 tmp = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
90 if (tmp == NULL) {
91 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
92 }
93
94 tmp->subcheck = subcheck;
95 tmp->next = check->subchecks;
96
97 check->subchecks = tmp;
98 }
99
100 return 0;
101}
102
103/*
104 * Add a mp_perfdata data point to a mp_subcheck object
105 */
106void mp_add_perfdata_to_subcheck(mp_subcheck check[static 1], const mp_perfdata perfData) {
107 if (check->perfdata == NULL) {
108 check->perfdata = pd_list_init();
109 }
110 pd_list_append(check->perfdata, perfData);
111}
112
113/*
114 * Add a mp_subcheck object to another one. The seconde mp_subcheck (argument) is the lower in the
115 * hierarchy
116 */
117int mp_add_subcheck_to_subcheck(mp_subcheck check[static 1], mp_subcheck subcheck) {
118 if (subcheck.output == NULL) {
119 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "Sub check output is NULL");
120 }
121
122 mp_subcheck_list *tmp = NULL;
123
124 if (check->subchecks == NULL) {
125 check->subchecks = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
126 if (check->subchecks == NULL) {
127 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
128 }
129
130 tmp = check->subchecks;
131 } else {
132 // Search for the end
133 tmp = check->subchecks;
134
135 while (tmp->next != NULL) {
136 tmp = tmp->next;
137 }
138
139 tmp->next = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list));
140 if (tmp->next == NULL) {
141 die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed");
142 }
143
144 tmp = tmp->next;
145 }
146
147 tmp->subcheck = subcheck;
148
149 return 0;
150}
151
152/*
153 * Add a manual summary to a mp_check object, effectively replacing
154 * the autogenerated one
155 */
156void mp_add_summary(mp_check check[static 1], char *summary) { check->summary = summary; }
157
158/*
159 * Generate the summary string of a mp_check object based on it's subchecks
160 */
161char *get_subcheck_summary(mp_check check) {
162 mp_subcheck_list *subchecks = check.subchecks;
163
164 unsigned int ok = 0;
165 unsigned int warning = 0;
166 unsigned int critical = 0;
167 unsigned int unknown = 0;
168 while (subchecks != NULL) {
169 switch (subchecks->subcheck.state) {
170 case STATE_OK:
171 ok++;
172 break;
173 case STATE_WARNING:
174 warning++;
175 break;
176 case STATE_CRITICAL:
177 critical++;
178 break;
179 case STATE_UNKNOWN:
180 unknown++;
181 break;
182 default:
183 die(STATE_UNKNOWN, "Unknown state in get_subcheck_summary");
184 }
185 subchecks = subchecks->next;
186 }
187 char *result = NULL;
188 xasprintf(&result, "ok=%d, warning=%d, critical=%d, unknown=%d", ok, warning, critical, unknown);
189 return result;
190}
191
192/*
193 * Generate the result state of a mp_subcheck object based on it's own state and it's subchecks states
194 */
195mp_state_enum mp_compute_subcheck_state(const mp_subcheck check) {
196 if (check.state_set_explicitly) {
197 return check.state;
198 }
199
200 mp_subcheck_list *scl = check.subchecks;
201 mp_state_enum result = check.default_state;
202
203 while (scl != NULL) {
204 result = max_state_alt(result, mp_compute_subcheck_state(scl->subcheck));
205 scl = scl->next;
206 }
207
208 return result;
209}
210
211/*
212 * Generate the result state of a mp_check object based on it's own state and it's subchecks states
213 */
214mp_state_enum mp_compute_check_state(const mp_check check) {
215 assert(check.subchecks != NULL); // a mp_check without subchecks is invalid, die here
216
217 mp_subcheck_list *scl = check.subchecks;
218 mp_state_enum result = STATE_OK;
219
220 while (scl != NULL) {
221 result = max_state_alt(result, mp_compute_subcheck_state(scl->subcheck));
222 scl = scl->next;
223 }
224
225 return result;
226}
227
228/*
229 * Generate output string for a mp_check object
230 * Non static to be available for testing functions
231 */
232char *mp_fmt_output(mp_check check) {
233 char *result = NULL;
234
235 switch (check.format) {
236 case MP_FORMAT_SUMMARY_ONLY:
237 if (check.summary == NULL) {
238 check.summary = get_subcheck_summary(check);
239 }
240
241 xasprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary);
242 return result;
243
244 case MP_FORMAT_ONE_LINE: {
245 /* SERVICE STATUS: First line of output | First part of performance data
246 * Any number of subsequent lines of output, but note that buffers
247 * may have a limited size | Second part of performance data, which
248 * may have continuation lines, too
249 */
250 if (check.summary == NULL) {
251 check.summary = get_subcheck_summary(check);
252 }
253
254 xasprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary);
255
256 mp_subcheck_list *subchecks = check.subchecks;
257
258 while (subchecks != NULL) {
259 xasprintf(&result, "%s - %s", result, fmt_subcheck_output(MP_FORMAT_ONE_LINE, subchecks->subcheck, 1));
260 subchecks = subchecks->next;
261 }
262
263 break;
264 }
265 case MP_FORMAT_ICINGA_WEB_2: {
266 if (check.summary == NULL) {
267 check.summary = get_subcheck_summary(check);
268 }
269
270 xasprintf(&result, "[%s] - %s", state_text(mp_compute_check_state(check)), check.summary);
271
272 mp_subcheck_list *subchecks = check.subchecks;
273
274 while (subchecks != NULL) {
275 xasprintf(&result, "%s\n%s", result, fmt_subcheck_output(MP_FORMAT_ICINGA_WEB_2, subchecks->subcheck, 1));
276 subchecks = subchecks->next;
277 }
278
279 char *pd_string = NULL;
280 subchecks = check.subchecks;
281
282 while (subchecks != NULL) {
283 if (pd_string == NULL) {
284 xasprintf(&pd_string, "%s", fmt_subcheck_perfdata(subchecks->subcheck));
285 } else {
286 xasprintf(&pd_string, "%s %s", pd_string, fmt_subcheck_perfdata(subchecks->subcheck));
287 }
288
289 subchecks = subchecks->next;
290 }
291
292 if (pd_string != NULL && strlen(pd_string) > 0) {
293 xasprintf(&result, "%s|%s", result, pd_string);
294 }
295
296 break;
297 }
298 case MP_FORMAT_TEST_JSON: {
299 cJSON *resultObject = cJSON_CreateObject();
300 if (resultObject == NULL) {
301 die(STATE_UNKNOWN, "cJSON_CreateObject failed");
302 }
303
304 cJSON *resultState = cJSON_CreateString(state_text(mp_compute_check_state(check)));
305 cJSON_AddItemToObject(resultObject, "state", resultState);
306
307 if (check.summary == NULL) {
308 check.summary = get_subcheck_summary(check);
309 }
310
311 cJSON *summary = cJSON_CreateString(check.summary);
312 cJSON_AddItemToObject(resultObject, "summary", summary);
313
314 if (check.subchecks != NULL) {
315 cJSON *subchecks = cJSON_CreateArray();
316
317 mp_subcheck_list *sc = check.subchecks;
318
319 while (sc != NULL) {
320 cJSON *sc_json = json_serialize_subcheck(sc->subcheck);
321 cJSON_AddItemToArray(subchecks, sc_json);
322 sc = sc->next;
323 }
324
325 cJSON_AddItemToObject(resultObject, "checks", subchecks);
326 }
327
328 result = cJSON_PrintUnformatted(resultObject);
329 break;
330 }
331 default:
332 die(STATE_UNKNOWN, "Invalid format");
333 }
334
335 return result;
336}
337
338/*
339 * Helper function to properly indent the output lines when using multiline
340 * formats
341 */
342static char *generate_indentation_string(unsigned int indentation) {
343 char *result = calloc(indentation + 1, sizeof(char));
344
345 for (unsigned int i = 0; i < indentation; i++) {
346 result[i] = '\t';
347 }
348
349 return result;
350}
351
352/*
353 * Helper function to generate the output string of mp_subcheck
354 */
355static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check, unsigned int indentation) {
356 char *result = NULL;
357
358 switch (output_format) {
359 case MP_FORMAT_ICINGA_WEB_2:
360 xasprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation), state_text(mp_compute_subcheck_state(check)),
361 check.output);
362
363 mp_subcheck_list *subchecks = check.subchecks;
364
365 while (subchecks != NULL) {
366 xasprintf(&result, "%s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1));
367 subchecks = subchecks->next;
368 }
369 return result;
370 case MP_FORMAT_ONE_LINE:
371 return result;
372 case MP_FORMAT_SUMMARY_ONLY:
373 return result;
374 default:
375 die(STATE_UNKNOWN, "Invalid format");
376 }
377}
378
379static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck) {
380 cJSON *result = cJSON_CreateObject();
381 cJSON *output = cJSON_CreateString(subcheck.output);
382 cJSON_AddItemToObject(result, "output", output);
383 cJSON *state = cJSON_CreateString(state_text(mp_compute_subcheck_state(subcheck)));
384 cJSON_AddItemToObject(result, "state", state);
385
386 if (subcheck.subchecks != NULL) {
387 cJSON *subchecks = cJSON_CreateArray();
388
389 mp_subcheck_list *sc = subcheck.subchecks;
390
391 while (sc != NULL) {
392 cJSON *sc_json = json_serialize_subcheck(sc->subcheck);
393 cJSON_AddItemToArray(subchecks, sc_json);
394 sc = sc->next;
395 }
396
397 cJSON_AddItemToObject(result, "checks", subchecks);
398 }
399
400 return result;
401}
402
403/*
404 * Wrapper function to print the output string of a mp_check object
405 * Use this in concrete plugins.
406 */
407void mp_print_output(mp_check check) { puts(mp_fmt_output(check)); }
408
409/*
410 * Convenience function to print the output string of a mp_check object and exit
411 * the program with the resulting state.
412 * Intended to be used to exit a monitoring plugin.
413 */
414void mp_exit(mp_check check) {
415 mp_print_output(check);
416 exit(mp_compute_check_state(check));
417}
418
419/*
420 * Function to set the result state of a mp_subcheck object explicitly.
421 * This will overwrite the default state AND states derived from it's subchecks
422 */
423mp_subcheck mp_set_subcheck_state(mp_subcheck check, mp_state_enum state) {
424 check.state = state;
425 check.state_set_explicitly = true;
426 return check;
427}
428
429/*
430 * Function to set the default result state of a mp_subcheck object. This state
431 * will be used if neither an explicit state is set (see *mp_set_subcheck_state*)
432 * nor does it include other subchecks
433 */
434mp_subcheck mp_set_subcheck_default_state(mp_subcheck check, mp_state_enum state) {
435 check.default_state = state;
436 return check;
437}
438
439char *mp_output_format_map[] = {
440 [MP_FORMAT_ONE_LINE] = "one-line",
441 [MP_FORMAT_ICINGA_WEB_2] = "icingaweb2",
442 [MP_FORMAT_SUMMARY_ONLY] = "summary-only",
443 [MP_FORMAT_TEST_JSON] = "mp-test-json",
444};
445
446/*
447 * Function to parse the output from a string
448 */
449parsed_output_format mp_parse_output_format(char *format_string) {
450 parsed_output_format result = {
451 .parsing_success = false,
452 .output_format = MP_FORMAT_DEFAULT,
453 };
454
455 for (mp_output_format i = 0; i < (sizeof(mp_output_format_map) / sizeof(char *)); i++) {
456 if (strcasecmp(mp_output_format_map[i], format_string) == 0) {
457 result.parsing_success = true;
458 result.output_format = i;
459 break;
460 }
461 }
462
463 return result;
464}