| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "health.h" |
| 4 | |
| 5 | void health_string2json(BUFFER *wb, const char *prefix, const char *label, const char *value, const char *suffix) { |
| 6 | if(value && *value) { |
| 7 | buffer_sprintf(wb, "%s\"%s\":\"", prefix, label); |
| 8 | buffer_strcat_htmlescape(wb, value); |
| 9 | buffer_strcat(wb, "\""); |
| 10 | buffer_strcat(wb, suffix); |
| 11 | } |
| 12 | else |
| 13 | buffer_sprintf(wb, "%s\"%s\":null%s", prefix, label, suffix); |
| 14 | } |
| 15 | |
| 16 | static inline void health_rrdcalc_values2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC *rc) { |
| 17 | (void)host; |
| 18 | buffer_sprintf(wb, |
| 19 | "\t\t\"%s.%s\": {\n" |
| 20 | "\t\t\t\"id\": %lu,\n" |
| 21 | , rrdcalc_chart_name(rc), rrdcalc_name(rc) |
| 22 | , (unsigned long)rc->id); |
| 23 | |
| 24 | buffer_strcat(wb, "\t\t\t\"value\":"); |
| 25 | buffer_print_netdata_double(wb, rc->value); |
| 26 | buffer_strcat(wb, ",\n"); |
| 27 | |
| 28 | buffer_strcat(wb, "\t\t\t\"last_updated\":"); |
| 29 | buffer_sprintf(wb, "%lu", (unsigned long)rc->last_updated); |
| 30 | buffer_strcat(wb, ",\n"); |
| 31 | |
| 32 | buffer_sprintf(wb, |
| 33 | "\t\t\t\"status\": \"%s\"\n" |
| 34 | , rrdcalc_status2string(rc->status)); |
| 35 | |
| 36 | buffer_strcat(wb, "\t\t}"); |
| 37 | } |
| 38 | |
| 39 | static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC *rc) { |
| 40 | char value_string[100 + 1]; |
| 41 | format_value_and_unit(value_string, 100, rc->value, rrdcalc_units(rc), -1); |
| 42 | |
| 43 | char hash_id[GUID_LEN + 1]; |
| 44 | uuid_unparse_lower(rc->config.hash_id, hash_id); |
| 45 | |
| 46 | buffer_sprintf(wb, |
| 47 | "\t\t\"%s.%s\": {\n" |
| 48 | "\t\t\t\"id\": %lu,\n" |
| 49 | "\t\t\t\"config_hash_id\": \"%s\",\n" |
| 50 | "\t\t\t\"name\": \"%s\",\n" |
| 51 | "\t\t\t\"chart\": \"%s\",\n" |
| 52 | "\t\t\t\"class\": \"%s\",\n" |
| 53 | "\t\t\t\"component\": \"%s\",\n" |
| 54 | "\t\t\t\"type\": \"%s\",\n" |
| 55 | "\t\t\t\"active\": %s,\n" |
| 56 | "\t\t\t\"disabled\": %s,\n" |
| 57 | "\t\t\t\"silenced\": %s,\n" |
| 58 | "\t\t\t\"exec\": \"%s\",\n" |
| 59 | "\t\t\t\"recipient\": \"%s\",\n" |
| 60 | "\t\t\t\"source\": \"%s\",\n" |
| 61 | "\t\t\t\"units\": \"%s\",\n" |
| 62 | "\t\t\t\"summary\": \"%s\",\n" |
| 63 | "\t\t\t\"info\": \"%s\",\n" |
| 64 | "\t\t\t\"status\": \"%s\",\n" |
| 65 | "\t\t\t\"last_status_change\": %lu,\n" |
| 66 | "\t\t\t\"last_updated\": %lu,\n" |
| 67 | "\t\t\t\"next_update\": %lu,\n" |
| 68 | "\t\t\t\"update_every\": %d,\n" |
| 69 | "\t\t\t\"delay_up_duration\": %d,\n" |
| 70 | "\t\t\t\"delay_down_duration\": %d,\n" |
| 71 | "\t\t\t\"delay_max_duration\": %d,\n" |
| 72 | "\t\t\t\"delay_multiplier\": %f,\n" |
| 73 | "\t\t\t\"delay\": %d,\n" |
| 74 | "\t\t\t\"delay_up_to_timestamp\": %lu,\n" |
| 75 | "\t\t\t\"warn_repeat_every\": \"%u\",\n" |
| 76 | "\t\t\t\"crit_repeat_every\": \"%u\",\n" |
| 77 | "\t\t\t\"value_string\": \"%s\",\n" |
| 78 | "\t\t\t\"last_repeat\": \"%lu\",\n" |
| 79 | "\t\t\t\"times_repeat\": %lu,\n" |
| 80 | , rrdcalc_chart_name(rc), rrdcalc_name(rc) |
| 81 | , (unsigned long)rc->id |
| 82 | , hash_id |
| 83 | , rrdcalc_name(rc) |
| 84 | , rrdcalc_chart_name(rc) |
| 85 | , rc->config.classification?rrdcalc_classification(rc):"Unknown" |
| 86 | , rc->config.component?rrdcalc_component(rc):"Unknown" |
| 87 | , rc->config.type?rrdcalc_type(rc):"Unknown" |
| 88 | , (rc->rrdset)?"true":"false" |
| 89 | , (rc->run_flags & RRDCALC_FLAG_DISABLED)?"true":"false" |
| 90 | , (rc->run_flags & RRDCALC_FLAG_SILENCED)?"true":"false" |
| 91 | , rc->config.exec?rrdcalc_exec(rc):string2str(host->health.default_exec) |
| 92 | , rc->config.recipient?rrdcalc_recipient(rc):string2str(host->health.default_recipient) |
| 93 | , rrdcalc_source(rc) |
| 94 | , rrdcalc_units(rc) |
| 95 | , string2str(rc->summary) |
| 96 | , string2str(rc->info) |
| 97 | , rrdcalc_status2string(rc->status) |
| 98 | , (unsigned long)rc->last_status_change |
| 99 | , (unsigned long)rc->last_updated |
| 100 | , (unsigned long)rc->next_update |
| 101 | , rc->config.update_every |
| 102 | , rc->config.delay_up_duration |
| 103 | , rc->config.delay_down_duration |
| 104 | , rc->config.delay_max_duration |
| 105 | , rc->config.delay_multiplier |
| 106 | , rc->delay_last |
| 107 | , (unsigned long)rc->delay_up_to_timestamp |
| 108 | , rc->config.warn_repeat_every |
| 109 | , rc->config.crit_repeat_every |
| 110 | , value_string |
| 111 | , (unsigned long)rc->last_repeat |
| 112 | , (unsigned long)rc->times_repeat |
| 113 | ); |
| 114 | |
| 115 | if(unlikely(rc->config.alert_action_options & ALERT_ACTION_OPTION_NO_CLEAR_NOTIFICATION)) { |
| 116 | buffer_strcat(wb, "\t\t\t\"no_clear_notification\": true,\n"); |
| 117 | } |
| 118 | |
| 119 | if(RRDCALC_HAS_DB_LOOKUP(rc)) { |
| 120 | if(rc->config.dimensions) |
| 121 | health_string2json(wb, "\t\t\t", "lookup_dimensions", rrdcalc_dimensions(rc), ",\n"); |
| 122 | |
| 123 | buffer_sprintf(wb, |
| 124 | "\t\t\t\"db_after\": %lu,\n" |
| 125 | "\t\t\t\"db_before\": %lu,\n" |
| 126 | "\t\t\t\"lookup_method\": \"%s\",\n" |
| 127 | "\t\t\t\"lookup_after\": %d,\n" |
| 128 | "\t\t\t\"lookup_before\": %d,\n" |
| 129 | "\t\t\t\"lookup_options\": \"", |
| 130 | (unsigned long) rc->db_after, |
| 131 | (unsigned long) rc->db_before, |
| 132 | time_grouping_id2txt(rc->config.time_group), |
| 133 | rc->config.after, |
| 134 | rc->config.before |
| 135 | ); |
| 136 | rrdr_options_to_buffer(wb, rc->config.options); |
| 137 | buffer_strcat(wb, "\",\n"); |
| 138 | } |
| 139 | |
| 140 | if(rc->config.calculation) { |
| 141 | health_string2json(wb, "\t\t\t", "calc", expression_source(rc->config.calculation), ",\n"); |
| 142 | health_string2json(wb, "\t\t\t", "calc_parsed", expression_parsed_as(rc->config.calculation), ",\n"); |
| 143 | } |
| 144 | |
| 145 | if(rc->config.warning) { |
| 146 | health_string2json(wb, "\t\t\t", "warn", expression_source(rc->config.warning), ",\n"); |
| 147 | health_string2json(wb, "\t\t\t", "warn_parsed", expression_parsed_as(rc->config.warning), ",\n"); |
| 148 | } |
| 149 | |
| 150 | if(rc->config.critical) { |
| 151 | health_string2json(wb, "\t\t\t", "crit", expression_source(rc->config.critical), ",\n"); |
| 152 | health_string2json(wb, "\t\t\t", "crit_parsed", expression_parsed_as(rc->config.critical), ",\n"); |
| 153 | } |
| 154 | |
| 155 | buffer_strcat(wb, "\t\t\t\"green\":"); |
| 156 | buffer_print_netdata_double(wb, NAN); |
| 157 | buffer_strcat(wb, ",\n"); |
| 158 | |
| 159 | buffer_strcat(wb, "\t\t\t\"red\":"); |
| 160 | buffer_print_netdata_double(wb, NAN); |
| 161 | buffer_strcat(wb, ",\n"); |
| 162 | |
| 163 | buffer_strcat(wb, "\t\t\t\"value\":"); |
| 164 | buffer_print_netdata_double(wb, rc->value); |
| 165 | buffer_strcat(wb, "\n"); |
| 166 | |
| 167 | buffer_strcat(wb, "\t\t}"); |
| 168 | } |
| 169 | |
| 170 | void health_aggregate_alarms(RRDHOST *host, BUFFER *wb, BUFFER* contexts, RRDCALC_STATUS status) { |
| 171 | RRDCALC *rc; |
| 172 | int numberOfAlarms = 0; |
| 173 | char *tok = NULL; |
| 174 | char *p = NULL; |
| 175 | |
| 176 | if (contexts) { |
| 177 | p = (char*)buffer_tostring(contexts); |
| 178 | while(p && *p && (tok = strsep_skip_consecutive_separators(&p, ", |"))) { |
| 179 | if(!*tok) continue; |
| 180 | |
| 181 | STRING *tok_string = string_strdupz(tok); |
| 182 | |
| 183 | foreach_rrdcalc_in_rrdhost_read(host, rc) { |
| 184 | if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) |
| 185 | continue; |
| 186 | if (unlikely(!rrdset_is_available_for_exporting_and_alarms(rc->rrdset))) |
| 187 | continue; |
| 188 | if(unlikely(rc->rrdset |
| 189 | && rc->rrdset->context == tok_string |
| 190 | && ((status==RRDCALC_STATUS_RAISED)?(rc->status >= RRDCALC_STATUS_WARNING):rc->status == status))) |
| 191 | numberOfAlarms++; |
| 192 | } |
| 193 | foreach_rrdcalc_in_rrdhost_done(rc); |
| 194 | |
| 195 | string_freez(tok_string); |
| 196 | } |
| 197 | } |
| 198 | else { |
| 199 | foreach_rrdcalc_in_rrdhost_read(host, rc) { |
| 200 | if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) |
| 201 | continue; |
| 202 | if (unlikely(!rrdset_is_available_for_exporting_and_alarms(rc->rrdset))) |
| 203 | continue; |
| 204 | if(unlikely((status==RRDCALC_STATUS_RAISED)?(rc->status >= RRDCALC_STATUS_WARNING):rc->status == status)) |
| 205 | numberOfAlarms++; |
| 206 | } |
| 207 | foreach_rrdcalc_in_rrdhost_done(rc); |
| 208 | } |
| 209 | |
| 210 | buffer_sprintf(wb, "%d", numberOfAlarms); |
| 211 | } |
| 212 | |
| 213 | static void health_alarms2json_fill_alarms(RRDHOST *host, BUFFER *wb, int all, void (*fp)(RRDHOST *, BUFFER *, RRDCALC *)) { |
| 214 | RRDCALC *rc; |
| 215 | int i = 0; |
| 216 | foreach_rrdcalc_in_rrdhost_read(host, rc) { |
| 217 | if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) |
| 218 | continue; |
| 219 | |
| 220 | if (unlikely(!rrdset_is_available_for_exporting_and_alarms(rc->rrdset))) |
| 221 | continue; |
| 222 | |
| 223 | if(likely(!all && !(rc->status == RRDCALC_STATUS_WARNING || rc->status == RRDCALC_STATUS_CRITICAL))) |
| 224 | continue; |
| 225 | |
| 226 | if(likely(i)) buffer_strcat(wb, ",\n"); |
| 227 | fp(host, wb, rc); |
| 228 | i++; |
| 229 | } |
| 230 | foreach_rrdcalc_in_rrdhost_done(rc); |
| 231 | } |
| 232 | |
| 233 | void health_alarms2json(RRDHOST *host, BUFFER *wb, int all) { |
| 234 | buffer_sprintf(wb, "{\n\t\"hostname\": \"%s\"," |
| 235 | "\n\t\"latest_alarm_log_unique_id\": %u," |
| 236 | "\n\t\"status\": %s," |
| 237 | "\n\t\"now\": %lu," |
| 238 | "\n\t\"alarms\": {\n", |
| 239 | rrdhost_hostname(host), |
| 240 | (host->health_log.next_log_id > 0)?(host->health_log.next_log_id - 1):0, |
| 241 | host->health.enabled ?"true":"false", |
| 242 | (unsigned long)now_realtime_sec()); |
| 243 | |
| 244 | health_alarms2json_fill_alarms(host, wb, all, health_rrdcalc2json_nolock); |
| 245 | |
| 246 | buffer_strcat(wb, "\n\t}\n}\n"); |
| 247 | } |
| 248 | |
| 249 | void health_alarms_values2json(RRDHOST *host, BUFFER *wb, int all) { |
| 250 | buffer_sprintf(wb, "{\n\t\"hostname\": \"%s\"," |
| 251 | "\n\t\"alarms\": {\n", |
| 252 | rrdhost_hostname(host)); |
| 253 | |
| 254 | health_alarms2json_fill_alarms(host, wb, all, health_rrdcalc_values2json_nolock); |
| 255 | |
| 256 | buffer_strcat(wb, "\n\t}\n}\n"); |
| 257 | } |
| 258 | |
| 259 | void health_entry_flags_to_json_array(BUFFER *wb, const char *key, HEALTH_ENTRY_FLAGS flags) { |
| 260 | buffer_json_member_add_array(wb, key); |
| 261 | |
| 262 | if(flags & HEALTH_ENTRY_FLAG_PROCESSED) |
| 263 | buffer_json_add_array_item_string(wb, "PROCESSED"); |
| 264 | if(flags & HEALTH_ENTRY_FLAG_UPDATED) |
| 265 | buffer_json_add_array_item_string(wb, "UPDATED"); |
| 266 | if(flags & HEALTH_ENTRY_FLAG_EXEC_RUN) |
| 267 | buffer_json_add_array_item_string(wb, "EXEC_RUN"); |
| 268 | if(flags & HEALTH_ENTRY_FLAG_EXEC_FAILED) |
| 269 | buffer_json_add_array_item_string(wb, "EXEC_FAILED"); |
| 270 | if(flags & HEALTH_ENTRY_FLAG_SILENCED) |
| 271 | buffer_json_add_array_item_string(wb, "SILENCED"); |
| 272 | if(flags & HEALTH_ENTRY_RUN_ONCE) |
| 273 | buffer_json_add_array_item_string(wb, "RUN_ONCE"); |
| 274 | if(flags & HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS) |
| 275 | buffer_json_add_array_item_string(wb, "EXEC_IN_PROGRESS"); |
| 276 | if(flags & HEALTH_ENTRY_FLAG_IS_REPEATING) |
| 277 | buffer_json_add_array_item_string(wb, "RECURRING"); |
| 278 | if(flags & HEALTH_ENTRY_FLAG_SAVED) |
| 279 | buffer_json_add_array_item_string(wb, "SAVED"); |
| 280 | if(flags & HEALTH_ENTRY_FLAG_ACLK_QUEUED) |
| 281 | buffer_json_add_array_item_string(wb, "ACLK_QUEUED"); |
| 282 | if(flags & HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION) |
| 283 | buffer_json_add_array_item_string(wb, "NO_CLEAR_NOTIFICATION"); |
| 284 | |
| 285 | buffer_json_array_close(wb); |
| 286 | } |