diff options
author | Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> | 2025-02-18 21:58:34 +0100 |
---|---|---|
committer | Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> | 2025-02-18 21:58:34 +0100 |
commit | 0645c9fc2c7f801ba3c7d68a17c137a63ada299f (patch) | |
tree | ce222906f546f03301defba9ce81ba57591a8eb3 /lib/output.c | |
parent | 39680498ee0987a5e0eb203a2c0539aa1fa94d39 (diff) | |
download | monitoring-plugins-0645c9fc2c7f801ba3c7d68a17c137a63ada299f.tar.gz |
Implement new output functionality
Diffstat (limited to 'lib/output.c')
-rw-r--r-- | lib/output.c | 464 |
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 == | ||
13 | static char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check, unsigned int indentation); | ||
14 | static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck); | ||
15 | |||
16 | // == Implementation == | ||
17 | |||
18 | /* | ||
19 | * Generate output string for a mp_subcheck object | ||
20 | */ | ||
21 | static 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 | */ | ||
54 | mp_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 | */ | ||
64 | mp_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 | */ | ||
74 | int 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 | */ | ||
106 | void 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 | */ | ||
117 | int 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 | */ | ||
156 | void 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 | */ | ||
161 | char *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 | */ | ||
195 | mp_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 | */ | ||
214 | mp_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 | */ | ||
232 | char *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 | */ | ||
342 | static 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 | */ | ||
355 | static 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 | |||
379 | static 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 | */ | ||
407 | void 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 | */ | ||
414 | void 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 | */ | ||
423 | mp_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 | */ | ||
434 | mp_subcheck mp_set_subcheck_default_state(mp_subcheck check, mp_state_enum state) { | ||
435 | check.default_state = state; | ||
436 | return check; | ||
437 | } | ||
438 | |||
439 | char *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 | */ | ||
449 | parsed_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 | } | ||