// SPDX-License-Identifier: GPL-3.0-or-later #include "json_wrapper.h" struct value_output { int c; BUFFER *wb; }; static int value_list_output_callback(const DICTIONARY_ITEM *item __maybe_unused, void *entry, void *data) { struct value_output *ap = (struct value_output *)data; BUFFER *wb = ap->wb; char *output = (char *) entry; if(ap->c) buffer_strcat(wb, ","); buffer_strcat(wb, output); (ap->c)++; return 0; } static int fill_formatted_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) { (void)ls; DICTIONARY *dict = (DICTIONARY *)data; char n[RRD_ID_LENGTH_MAX * 2 + 2]; char output[RRD_ID_LENGTH_MAX * 2 + 8]; char v[RRD_ID_LENGTH_MAX * 2 + 1]; sanitize_json_string(v, (char *)value, RRD_ID_LENGTH_MAX * 2); int len = snprintfz(output, RRD_ID_LENGTH_MAX * 2 + 7, "[\"%s\", \"%s\"]", name, v); snprintfz(n, RRD_ID_LENGTH_MAX * 2, "%s:%s", name, v); dictionary_set(dict, n, output, len + 1); return 1; } void rrdr_show_plan(RRDR *r, BUFFER *wb, const char *kq, const char *sq __maybe_unused) { QUERY_TARGET *qt = r->internal.qt; buffer_sprintf(wb, "\n\t%squery_plan%s: {", kq, kq); for(size_t m = 0; m < qt->query.used; m++) { QUERY_METRIC *qm = &qt->query.array[m]; if(m) buffer_strcat(wb, ","); buffer_sprintf(wb, "\n\t\t%s%s%s: {", kq, string2str(qm->dimension.id), kq); buffer_sprintf(wb, "\n\t\t\t%splans%s: [", kq, kq); for(size_t p = 0; p < qm->plan.used ;p++) { QUERY_PLAN_ENTRY *qp = &qm->plan.array[p]; if(p) buffer_strcat(wb, ","); buffer_strcat(wb, "\n\t\t\t\t{"); buffer_sprintf(wb, "\n\t\t\t\t\t%stier%s: %zu,", kq, kq, qp->tier); buffer_sprintf(wb, "\n\t\t\t\t\t%safter%s: %ld,", kq, kq, qp->after); buffer_sprintf(wb, "\n\t\t\t\t\t%sbefore%s: %ld", kq, kq, qp->before); buffer_strcat(wb, "\n\t\t\t\t}"); } buffer_strcat(wb, "\n\t\t\t],"); buffer_sprintf(wb, "\n\t\t\t%stiers%s: [", kq, kq); for(size_t tier = 0; tier < storage_tiers ;tier++) { if(tier) buffer_strcat(wb, ","); buffer_strcat(wb, "\n\t\t\t\t{"); buffer_sprintf(wb, "\n\t\t\t\t\t%stier%s: %zu,", kq, kq, tier); buffer_sprintf(wb, "\n\t\t\t\t\t%sdb_first_time%s: %ld,", kq, kq, qm->tiers[tier].db_first_time_s); buffer_sprintf(wb, "\n\t\t\t\t\t%sdb_last_time%s: %ld,", kq, kq, qm->tiers[tier].db_last_time_s); buffer_sprintf(wb, "\n\t\t\t\t\t%sweight%s: %ld", kq, kq, qm->tiers[tier].weight); buffer_strcat(wb, "\n\t\t\t\t}"); } buffer_strcat(wb, "\n\t\t\t]"); buffer_strcat(wb, "\n\t\t}"); } buffer_strcat(wb, "\n\t},"); } void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value, RRDR_GROUPING group_method) { QUERY_TARGET *qt = r->internal.qt; long rows = rrdr_rows(r); long c, i; const long query_used = qt->query.used; //info("JSONWRAPPER(): %s: BEGIN", r->st->id); char kq[2] = "", // key quote sq[2] = ""; // string quote if( options & RRDR_OPTION_GOOGLE_JSON ) { kq[0] = '\0'; sq[0] = '\''; } else { kq[0] = '"'; sq[0] = '"'; } buffer_sprintf(wb, "{\n" " %sapi%s: 1,\n" " %sid%s: %s%s%s,\n" " %sname%s: %s%s%s,\n" " %sview_update_every%s: %lld,\n" " %supdate_every%s: %lld,\n" " %sfirst_entry%s: %lld,\n" " %slast_entry%s: %lld,\n" " %sbefore%s: %lld,\n" " %safter%s: %lld,\n" " %sgroup%s: %s%s%s,\n" " %soptions%s: %s" , kq, kq , kq, kq, sq, qt->id, sq , kq, kq, sq, qt->id, sq , kq, kq, (long long)r->update_every , kq, kq, (long long)qt->db.minimum_latest_update_every_s , kq, kq, (long long)qt->db.first_time_s , kq, kq, (long long)qt->db.last_time_s , kq, kq, (long long)r->before , kq, kq, (long long)r->after , kq, kq, sq, web_client_api_request_v1_data_group_to_string(group_method), sq , kq, kq, sq); web_client_api_request_v1_data_options_to_buffer(wb, r->internal.query_options); buffer_sprintf(wb, "%s,\n %sdimension_names%s: [", sq, kq, kq); for(c = 0, i = 0; c < query_used ; c++) { if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; if(i) buffer_strcat(wb, ", "); buffer_strcat(wb, sq); buffer_strcat(wb, string2str(qt->query.array[c].dimension.name)); buffer_strcat(wb, sq); i++; } if(!i) { #ifdef NETDATA_INTERNAL_CHECKS error("QUERY: '%s', RRDR is empty, %zu dimensions, options is 0x%08x", qt->id, r->d, options); #endif rows = 0; buffer_strcat(wb, sq); buffer_strcat(wb, "no data"); buffer_strcat(wb, sq); } buffer_sprintf(wb, "],\n" " %sdimension_ids%s: [" , kq, kq); for(c = 0, i = 0; c < query_used ; c++) { if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; if(i) buffer_strcat(wb, ", "); buffer_strcat(wb, sq); buffer_strcat(wb, string2str(qt->query.array[c].dimension.id)); buffer_strcat(wb, sq); i++; } if(!i) { rows = 0; buffer_strcat(wb, sq); buffer_strcat(wb, "no data"); buffer_strcat(wb, sq); } buffer_strcat(wb, "],\n"); if (r->internal.query_options & RRDR_OPTION_ALL_DIMENSIONS) { buffer_sprintf(wb, " %sfull_dimension_list%s: [", kq, kq); char name[RRD_ID_LENGTH_MAX * 2 + 2]; char output[RRD_ID_LENGTH_MAX * 2 + 8]; struct value_output co = {.c = 0, .wb = wb}; DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); for (c = 0; c < (long)qt->metrics.used ;c++) { snprintfz(name, RRD_ID_LENGTH_MAX * 2 + 1, "%s:%s", rrdmetric_acquired_id(qt->metrics.array[c]), rrdmetric_acquired_name(qt->metrics.array[c])); int len = snprintfz(output, RRD_ID_LENGTH_MAX * 2 + 7, "[\"%s\",\"%s\"]", rrdmetric_acquired_id(qt->metrics.array[c]), rrdmetric_acquired_name(qt->metrics.array[c])); dictionary_set(dict, name, output, len + 1); } dictionary_walkthrough_read(dict, value_list_output_callback, &co); dictionary_destroy(dict); co.c = 0; buffer_sprintf(wb, "],\n %sfull_chart_list%s: [", kq, kq); dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); for (c = 0; c < (long)qt->instances.used ; c++) { RRDINSTANCE_ACQUIRED *ria = qt->instances.array[c]; snprintfz(name, RRD_ID_LENGTH_MAX * 2 + 1, "%s:%s", rrdinstance_acquired_id(ria), rrdinstance_acquired_name(ria)); int len = snprintfz(output, RRD_ID_LENGTH_MAX * 2 + 7, "[\"%s\",\"%s\"]", rrdinstance_acquired_id(ria), rrdinstance_acquired_name(ria)); dictionary_set(dict, name, output, len + 1); } dictionary_walkthrough_read(dict, value_list_output_callback, &co); dictionary_destroy(dict); co.c = 0; buffer_sprintf(wb, "],\n %sfull_chart_labels%s: [", kq, kq); dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); for (c = 0; c < (long)qt->instances.used ; c++) { RRDINSTANCE_ACQUIRED *ria = qt->instances.array[c]; rrdlabels_walkthrough_read(rrdinstance_acquired_labels(ria), fill_formatted_callback, dict); } dictionary_walkthrough_read(dict, value_list_output_callback, &co); dictionary_destroy(dict); buffer_strcat(wb, "],\n"); } // functions { DICTIONARY *funcs = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); RRDINSTANCE_ACQUIRED *ria = NULL; for (c = 0; c < query_used ; c++) { QUERY_METRIC *qm = &qt->query.array[c]; if(qm->link.ria == ria) continue; ria = qm->link.ria; chart_functions_to_dict(rrdinstance_acquired_functions(ria), funcs); } buffer_sprintf(wb, " %sfunctions%s: [", kq, kq); void *t; (void)t; dfe_start_read(funcs, t) { const char *comma = ""; if(t_dfe.counter) comma = ", "; buffer_sprintf(wb, "%s%s%s%s", comma, sq, t_dfe.name, sq); } dfe_done(t); dictionary_destroy(funcs); buffer_strcat(wb, "],\n"); } // context query if (!qt->request.st) { buffer_sprintf( wb, " %schart_ids%s: [", kq, kq); for (c = 0, i = 0; c < query_used; c++) { QUERY_METRIC *qm = &qt->query.array[c]; if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; if (unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; if (i) buffer_strcat(wb, ", "); buffer_strcat(wb, sq); buffer_strcat(wb, string2str(qm->chart.id)); buffer_strcat(wb, sq); i++; } if (!i) { rows = 0; buffer_strcat(wb, sq); buffer_strcat(wb, "no data"); buffer_strcat(wb, sq); } buffer_strcat(wb, "],\n"); if (qt->instances.chart_label_key_pattern) { buffer_sprintf(wb, " %schart_labels%s: { ", kq, kq); SIMPLE_PATTERN *pattern = qt->instances.chart_label_key_pattern; char *label_key = NULL; int keys = 0; while (pattern && (label_key = simple_pattern_iterate(&pattern))) { if (keys) buffer_strcat(wb, ", "); buffer_sprintf(wb, "%s%s%s : [", kq, label_key, kq); keys++; for (c = 0, i = 0; c < query_used; c++) { QUERY_METRIC *qm = &qt->query.array[c]; if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; if (unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; if (i) buffer_strcat(wb, ", "); rrdlabels_get_value_to_buffer_or_null(rrdinstance_acquired_labels(qm->link.ria), wb, label_key, sq, "null"); i++; } if (!i) { rows = 0; buffer_strcat(wb, sq); buffer_strcat(wb, "no data"); buffer_strcat(wb, sq); } buffer_strcat(wb, "]"); } buffer_strcat(wb, "},\n"); } } buffer_sprintf(wb, " %slatest_values%s: [" , kq, kq); for(c = 0, i = 0; c < query_used ;c++) { QUERY_METRIC *qm = &qt->query.array[c]; if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; if(i) buffer_strcat(wb, ", "); i++; NETDATA_DOUBLE value = rrdmetric_acquired_last_stored_value(qm->link.rma); if (NAN == value) buffer_strcat(wb, "null"); else buffer_rrd_value(wb, value); } if(!i) { rows = 0; buffer_strcat(wb, "null"); } buffer_sprintf(wb, "],\n" " %sview_latest_values%s: [" , kq, kq); i = 0; if(rows) { NETDATA_DOUBLE total = 1; if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { total = 0; for(c = 0; c < query_used ;c++) { NETDATA_DOUBLE *cn = &r->v[ (rrdr_rows(r) - 1) * r->d ]; NETDATA_DOUBLE n = cn[c]; if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) n = -n; total += n; } // prevent a division by zero if(total == 0) total = 1; } for(c = 0, i = 0; c < query_used ;c++) { if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; if(i) buffer_strcat(wb, ", "); i++; NETDATA_DOUBLE *cn = &r->v[ (rrdr_rows(r) - 1) * r->d ]; RRDR_VALUE_FLAGS *co = &r->o[ (rrdr_rows(r) - 1) * r->d ]; NETDATA_DOUBLE n = cn[c]; if(co[c] & RRDR_VALUE_EMPTY) { if(options & RRDR_OPTION_NULL2ZERO) buffer_strcat(wb, "0"); else buffer_strcat(wb, "null"); } else { if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) n = -n; if(unlikely(options & RRDR_OPTION_PERCENTAGE)) n = n * 100 / total; buffer_rrd_value(wb, n); } } } if(!i) { rows = 0; buffer_strcat(wb, "null"); } buffer_sprintf(wb, "],\n" " %sdimensions%s: %ld,\n" " %spoints%s: %ld,\n" " %sformat%s: %s" , kq, kq, i , kq, kq, rows , kq, kq, sq ); rrdr_buffer_print_format(wb, format); buffer_sprintf(wb, "%s,\n" " %sdb_points_per_tier%s: [ " , sq , kq, kq ); for(size_t tier = 0; tier < storage_tiers ; tier++) buffer_sprintf(wb, "%s%zu", tier>0?", ":"", r->internal.tier_points_read[tier]); buffer_strcat(wb, " ],"); if(options & RRDR_OPTION_SHOW_PLAN) rrdr_show_plan(r, wb, kq, sq); buffer_sprintf(wb, "\n %sresult%s: ", kq, kq); if(string_value) buffer_strcat(wb, sq); //info("JSONWRAPPER(): %s: END", r->st->id); } void rrdr_json_wrapper_anomaly_rates(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value) { (void)r; (void)format; char kq[2] = "", // key quote sq[2] = ""; // string quote if( options & RRDR_OPTION_GOOGLE_JSON ) { kq[0] = '\0'; sq[0] = '\''; } else { kq[0] = '"'; sq[0] = '"'; } if(string_value) buffer_strcat(wb, sq); buffer_sprintf(wb, ",\n %sanomaly_rates%s: ", kq, kq); } void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value) { (void)format; char kq[2] = "", // key quote sq[2] = ""; // string quote if( options & RRDR_OPTION_GOOGLE_JSON ) { kq[0] = '\0'; sq[0] = '\''; } else { kq[0] = '"'; sq[0] = '"'; } if(string_value) buffer_strcat(wb, sq); buffer_sprintf(wb, ",\n %smin%s: ", kq, kq); buffer_rrd_value(wb, r->min); buffer_sprintf(wb, ",\n %smax%s: ", kq, kq); buffer_rrd_value(wb, r->max); buffer_strcat(wb, "\n}\n"); }