diff options
Diffstat (limited to 'web/api/formatters/rrd2json.c')
-rw-r--r-- | web/api/formatters/rrd2json.c | 463 |
1 files changed, 422 insertions, 41 deletions
diff --git a/web/api/formatters/rrd2json.c b/web/api/formatters/rrd2json.c index 1ea6d23a64..a892019272 100644 --- a/web/api/formatters/rrd2json.c +++ b/web/api/formatters/rrd2json.c @@ -7,7 +7,7 @@ void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb) { rrdset2json(st, wb, NULL, NULL, 0); } -const char *rrdr_format_to_string(uint32_t format) { +const char *rrdr_format_to_string(DATASOURCE_FORMAT format) { switch(format) { case DATASOURCE_JSON: return DATASOURCE_FORMAT_JSON; @@ -105,15 +105,15 @@ int rrdset2value_api_v1( } if(db_points_read) - *db_points_read += r->internal.db_points_read; + *db_points_read += r->stats.db_points_read; if(db_points_per_tier) { for(size_t t = 0; t < storage_tiers ;t++) - db_points_per_tier[t] += r->internal.tier_points_read[t]; + db_points_per_tier[t] += r->stats.tier_points_read[t]; } if(result_points_generated) - *result_points_generated += r->internal.result_points_generated; + *result_points_generated += r->stats.result_points_generated; if(rrdr_rows(r) == 0) { if(db_after) *db_after = 0; @@ -125,14 +125,14 @@ int rrdset2value_api_v1( } if(wb) { - if (r->result_options & RRDR_RESULT_OPTION_RELATIVE) + if (r->view.flags & RRDR_RESULT_FLAG_RELATIVE) buffer_no_cacheable(wb); - else if (r->result_options & RRDR_RESULT_OPTION_ABSOLUTE) + else if (r->view.flags & RRDR_RESULT_FLAG_ABSOLUTE) buffer_cacheable(wb); } - if(db_after) *db_after = r->after; - if(db_before) *db_before = r->before; + if(db_after) *db_after = r->view.after; + if(db_before) *db_before = r->view.before; long i = (!(options & RRDR_OPTION_REVERSED))?(long)rrdr_rows(r) - 1:0; *n = rrdr2value(r, i, options, value_is_null, anomaly_rate); @@ -144,38 +144,412 @@ cleanup: return ret; } +struct group_by_entry { + size_t priority; + size_t count; + STRING *id; + STRING *name; + STRING *units; +}; + +static int group_by_label_is_space(char c) { + if(c == ',' || c == '|') + return 1; + + return 0; +} + +RRDR *data_query_group_by(RRDR *r) { + QUERY_TARGET *qt = r->internal.qt; + RRDR_OPTIONS options = qt->request.options; + size_t rows = rrdr_rows(r); + + if(qt->request.group_by == RRDR_GROUP_BY_NONE || !rows) + return r; + + struct group_by_entry *entries = onewayalloc_callocz(r->internal.owa, qt->query.used, sizeof(struct group_by_entry)); + DICTIONARY *groups = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); + + if(qt->request.group_by & RRDR_GROUP_BY_LABEL && qt->request.group_by_label && *qt->request.group_by_label) + qt->group_by.used = quoted_strings_splitter(qt->request.group_by_label, qt->group_by.label_keys, GROUP_BY_MAX_LABEL_KEYS, group_by_label_is_space); + + if(!qt->group_by.used) + qt->request.group_by &= ~RRDR_GROUP_BY_LABEL; + + if(!(qt->request.group_by & (RRDR_GROUP_BY_NODE | RRDR_GROUP_BY_INSTANCE | RRDR_GROUP_BY_DIMENSION | RRDR_GROUP_BY_LABEL))) + qt->request.group_by = RRDR_GROUP_BY_DIMENSION; + + int added = 0; + BUFFER *key = buffer_create(0, NULL); + QUERY_INSTANCE *last_qi = NULL; + size_t priority = 0; + for(size_t c = 0; c < qt->query.used ;c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + QUERY_METRIC *qm = query_metric(qt, c); + QUERY_INSTANCE *qi = query_instance(qt, qm->link.query_instance_id); + QUERY_HOST *qh = query_host(qt, qm->link.query_host_id); + + if(qi != last_qi) { + priority = 0; + last_qi = qi; + } + else + priority++; + + // generate the group by key + + buffer_flush(key); + if(qt->request.group_by & RRDR_GROUP_BY_DIMENSION) { + buffer_fast_strcat(key, "|", 1); + buffer_strcat(key, query_metric_id(qt, qm)); + } + if(qt->request.group_by & RRDR_GROUP_BY_INSTANCE) { + buffer_fast_strcat(key, "|", 1); + buffer_strcat(key, string2str(qi->id_fqdn)); + } + if(qt->request.group_by & RRDR_GROUP_BY_LABEL) { + DICTIONARY *labels = rrdinstance_acquired_labels(qi->ria); + for(size_t l = 0; l < qt->group_by.used ;l++) { + buffer_fast_strcat(key, "|", 1); + rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by.label_keys[l], "[unset]"); + } + } + if(qt->request.group_by & RRDR_GROUP_BY_NODE) { + buffer_fast_strcat(key, "|", 1); + buffer_strcat(key, qh->host->machine_guid); + } + buffer_fast_strcat(key, "|", 1); + buffer_strcat(key, rrdinstance_acquired_units(qi->ria)); + + // lookup the key in the dictionary + + int pos = -1; + int *set = dictionary_set(groups, buffer_tostring(key), &pos, sizeof(pos)); + if(*set == -1) { + // the key just added to the dictionary + + *set = pos = added++; + + // generate the dimension id + + buffer_flush(key); + if(qt->request.group_by & RRDR_GROUP_BY_DIMENSION) { + buffer_strcat(key, query_metric_id(qt, qm)); + } + if(qt->request.group_by & RRDR_GROUP_BY_INSTANCE) { + if(buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + + if(qt->request.group_by & RRDR_GROUP_BY_NODE) + buffer_strcat(key, rrdinstance_acquired_id(qi->ria)); + else + buffer_strcat(key, string2str(qi->id_fqdn)); + } + if(qt->request.group_by & RRDR_GROUP_BY_LABEL) { + DICTIONARY *labels = rrdinstance_acquired_labels(qi->ria); + for(size_t l = 0; l < qt->group_by.used ;l++) { + if(buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by.label_keys[l], "[unset]"); + } + } + if(qt->request.group_by & RRDR_GROUP_BY_NODE) { + if(buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + + buffer_strcat(key, qh->host->machine_guid); + } + entries[pos].id = string_strdupz(buffer_tostring(key)); + + // generate the dimension name + + buffer_flush(key); + if(qt->request.group_by & RRDR_GROUP_BY_DIMENSION) { + buffer_strcat(key, query_metric_name(qt, qm)); + } + if(qt->request.group_by & RRDR_GROUP_BY_INSTANCE) { + if(buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + + if(qt->request.group_by & RRDR_GROUP_BY_NODE) + buffer_strcat(key, rrdinstance_acquired_name(qi->ria)); + else + buffer_strcat(key, string2str(qi->name_fqdn)); + } + if(qt->request.group_by & RRDR_GROUP_BY_LABEL) { + DICTIONARY *labels = rrdinstance_acquired_labels(qi->ria); + for(size_t l = 0; l < qt->group_by.used ;l++) { + if(buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by.label_keys[l], "[unset]"); + } + } + if(qt->request.group_by & RRDR_GROUP_BY_NODE) { + if(buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + + buffer_strcat(key, rrdhost_hostname(qh->host)); + } + entries[pos].name = string_strdupz(buffer_tostring(key)); + + // add the rest of the info + entries[pos].units = rrdinstance_acquired_units_dup(qi->ria); + entries[pos].priority = priority; + } + else { + // the key found in the dictionary + pos = *set; + } + + entries[pos].count++; + + if(unlikely(priority < entries[pos].priority)) + entries[pos].priority = priority; + + qm->grouped_as.slot = pos; + qm->grouped_as.id = entries[pos].id; + qm->grouped_as.name = entries[pos].name; + qm->grouped_as.units = entries[pos].units; + qm->status |= RRDR_DIMENSION_GROUPED; + } + + // check if we have multiple units + bool multiple_units = false; + for(int i = 1; i < added ; i++) { + if(entries[i].units != entries[0].units) { + multiple_units = true; + break; + } + } + + if(multiple_units) { + // include the units into the id and name of the dimensions + for(int i = 0; i < added ; i++) { + buffer_flush(key); + buffer_strcat(key, string2str(entries[i].id)); + buffer_fast_strcat(key, ",", 1); + buffer_strcat(key, string2str(entries[i].units)); + STRING *u = string_strdupz(buffer_tostring(key)); + string_freez(entries[i].id); + entries[i].id = u; + } + } + + RRDR *r2 = rrdr_create(r->internal.owa, qt, added, rows); + if(!r2) + goto cleanup; + + r2->dp = onewayalloc_callocz(r2->internal.owa, r2->d, sizeof(*r2->dp)); + r2->dgbc = onewayalloc_callocz(r2->internal.owa, r2->d, sizeof(*r2->dgbc)); + r2->gbc = onewayalloc_callocz(r2->internal.owa, r2->n * r2->d, sizeof(*r2->gbc)); + + // copy from previous rrdr + r2->view = r->view; + r2->stats = r->stats; + r2->rows = rows; + r2->stats.result_points_generated = r2->d * r2->n; + + // initialize r2 (dimension options, names, and ids) + for(size_t c2 = 0; c2 < r2->d ; c2++) { + r2->od[c2] = RRDR_DIMENSION_QUERIED; + r2->di[c2] = entries[c2].id; + r2->dn[c2] = entries[c2].name; + r2->du[c2] = entries[c2].units; + r2->dp[c2] = entries[c2].priority; + r2->dgbc[c2] = entries[c2].count; + } + + // initialize r2 (timestamps and value flags) + for(size_t i = 0; i != rows ;i++) { + // copy the timestamp + r2->t[i] = r->t[i]; + + // make all values empty + NETDATA_DOUBLE *cn2 = &r2->v[ i * r2->d ]; + RRDR_VALUE_FLAGS *co2 = &r2->o[ i * r2->d ]; + NETDATA_DOUBLE *ar2 = &r2->ar[ i * r2->d ]; + for (size_t c2 = 0; c2 < r2->d; c2++) { + cn2[c2] = 0.0; + ar2[c2] = 0.0; + co2[c2] = RRDR_VALUE_EMPTY; + } + } + + // do the group_by + for(size_t i = 0; i != rows ;i++) { + size_t idx = i * r->d; + size_t idx2 = i * r2->d; + + NETDATA_DOUBLE *cn = &r->v[ idx ]; + RRDR_VALUE_FLAGS *co = &r->o[ idx ]; + NETDATA_DOUBLE *ar = &r->ar[ idx ]; + + NETDATA_DOUBLE *cn2 = &r2->v[ idx2 ]; + RRDR_VALUE_FLAGS *co2 = &r2->o[ idx2 ]; + NETDATA_DOUBLE *ar2 = &r2->ar[ idx2 ]; + uint32_t *gbc2 = &r2->gbc[ idx2 ]; + + for(size_t c = 0; c < r->d ;c++) { + if (!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + NETDATA_DOUBLE n = cn[c]; + RRDR_VALUE_FLAGS o = co[c]; + + if(o & RRDR_VALUE_EMPTY) { + if(options & RRDR_OPTION_NULL2ZERO) + n = 0.0; + else + continue; + } + + if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + QUERY_METRIC *qm = query_metric(qt, c); + size_t c2 = qm->grouped_as.slot; + + switch(qt->request.group_by_aggregate_function) { + default: + case RRDR_GROUP_BY_FUNCTION_AVERAGE: + case RRDR_GROUP_BY_FUNCTION_SUM: + case RRDR_GROUP_BY_FUNCTION_SUM_COUNT: + cn2[c2] += n; + break; + + case RRDR_GROUP_BY_FUNCTION_MIN: + if(n < cn2[c2]) + cn2[c2] = n; + break; + + case RRDR_GROUP_BY_FUNCTION_MAX: + if(n > cn2[c2]) + cn2[c2] = n; + break; + } + + if(o & RRDR_VALUE_RESET) + co2[c2] |= RRDR_VALUE_RESET; + + ar2[c2] += ar[c]; + gbc2[c2]++; + } + } + + // apply averaging, remove RRDR_VALUE_EMPTY, find the non-zero dimensions, min and max + size_t values = 0; + NETDATA_DOUBLE min = NAN, max = NAN; + for (size_t c2 = 0; c2 < r2->d; c2++) { + size_t non_zero = 0; + + for(size_t i = 0; i != rows ;i++) { + size_t idx2 = i * r2->d + c2; + + NETDATA_DOUBLE *cn2 = &r2->v[ idx2 ]; + RRDR_VALUE_FLAGS *co2 = &r2->o[ idx2 ]; + NETDATA_DOUBLE *ar2 = &r2->ar[ idx2 ]; + uint32_t *gbc2 = &r2->gbc[ idx2 ]; + + if(likely(*gbc2)) { + *co2 &= ~RRDR_VALUE_EMPTY; + + *ar2 /= *gbc2; + + NETDATA_DOUBLE n; + + if(qt->request.group_by_aggregate_function == RRDR_GROUP_BY_FUNCTION_SUM_COUNT) { + n = *cn2 / *gbc2; + } + else if(qt->request.group_by_aggregate_function == RRDR_GROUP_BY_FUNCTION_AVERAGE) { + n = *cn2 / *gbc2; + *cn2 = n; + } + else + n = *cn2; + + if(islessgreater(n, 0.0)) + non_zero++; + + if(unlikely(!values++)) { + min = n; + max = n; + } + else { + if(n < min) + min = n; + + if(n > max) + max = n; + } + } + } + + if(non_zero) + r2->od[c2] |= RRDR_DIMENSION_NONZERO; + } + + r2->view.min = min; + r2->view.max = max; + +cleanup: + buffer_free(key); + + if(!r2 && entries && added) { + for(long c = 0; c < added ;c++) { + string_freez(entries[c].id); + string_freez(entries[c].name); + } + } + onewayalloc_freez(r->internal.owa, entries); + dictionary_destroy(groups); + + return r2; +} + int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *latest_timestamp) { + wrapper_begin_t wrapper_begin = rrdr_json_wrapper_begin; + wrapper_end_t wrapper_end = rrdr_json_wrapper_end; - RRDR *r = rrd2rrdr(owa, qt); - if(!r) { + if(qt->request.version == 2) + wrapper_begin = rrdr_json_wrapper_begin2; + + RRDR *r1 = rrd2rrdr(owa, qt); + qt->timings.executed_ut = now_monotonic_usec(); + + if(!r1) { buffer_strcat(wb, "Cannot generate output with these parameters on this chart."); return HTTP_RESP_INTERNAL_SERVER_ERROR; } - if (r->result_options & RRDR_RESULT_OPTION_CANCEL) { - rrdr_free(owa, r); + if (r1->view.flags & RRDR_RESULT_FLAG_CANCEL) { + rrdr_free(owa, r1); return HTTP_RESP_BACKEND_FETCH_FAILED; } - if(r->result_options & RRDR_RESULT_OPTION_RELATIVE) + if(r1->view.flags & RRDR_RESULT_FLAG_RELATIVE) buffer_no_cacheable(wb); - else if(r->result_options & RRDR_RESULT_OPTION_ABSOLUTE) + else if(r1->view.flags & RRDR_RESULT_FLAG_ABSOLUTE) buffer_cacheable(wb); - if(latest_timestamp && rrdr_rows(r) > 0) - *latest_timestamp = r->before; + if(latest_timestamp && rrdr_rows(r1) > 0) + *latest_timestamp = r1->view.before; DATASOURCE_FORMAT format = qt->request.format; RRDR_OPTIONS options = qt->request.options; RRDR_TIME_GROUPING group_method = qt->request.time_group_method; + RRDR *r = data_query_group_by(r1); + qt->timings.group_by_ut = now_monotonic_usec(); + switch(format) { case DATASOURCE_SSV: if(options & RRDR_OPTION_JSON_WRAP) { wb->content_type = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wrapper_begin(r, wb, format, options, true, group_method); rrdr2ssv(r, wb, options, "", " ", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + wrapper_end(r, wb, format, options, true); } else { wb->content_type = CT_TEXT_PLAIN; @@ -186,9 +560,9 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l case DATASOURCE_SSV_COMMA: if(options & RRDR_OPTION_JSON_WRAP) { wb->content_type = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wrapper_begin(r, wb, format, options, true, group_method); rrdr2ssv(r, wb, options, "", ",", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + wrapper_end(r, wb, format, options, true); } else { wb->content_type = CT_TEXT_PLAIN; @@ -199,9 +573,9 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l case DATASOURCE_JS_ARRAY: if(options & RRDR_OPTION_JSON_WRAP) { wb->content_type = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + wrapper_begin(r, wb, format, options, false, group_method); rrdr2ssv(r, wb, options, "[", ",", "]"); - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb, format, options, false); } else { wb->content_type = CT_APPLICATION_JSON; @@ -212,9 +586,9 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l case DATASOURCE_CSV: if(options & RRDR_OPTION_JSON_WRAP) { wb->content_type = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wrapper_begin(r, wb, format, options, true, group_method); rrdr2csv(r, wb, format, options, "", ",", "\\n", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + wrapper_end(r, wb, format, options, true); } else { wb->content_type = CT_TEXT_PLAIN; @@ -225,9 +599,9 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l case DATASOURCE_CSV_MARKDOWN: if(options & RRDR_OPTION_JSON_WRAP) { wb->content_type = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wrapper_begin(r, wb, format, options, true, group_method); rrdr2csv(r, wb, format, options, "", "|", "\\n", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + wrapper_end(r, wb, format, options, true); } else { wb->content_type = CT_TEXT_PLAIN; @@ -238,11 +612,11 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l case DATASOURCE_CSV_JSON_ARRAY: wb->content_type = CT_APPLICATION_JSON; if(options & RRDR_OPTION_JSON_WRAP) { - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + wrapper_begin(r, wb, format, options, false, group_method); buffer_strcat(wb, "[\n"); rrdr2csv(r, wb, format, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n"); buffer_strcat(wb, "\n]"); - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb, format, options, false); } else { wb->content_type = CT_APPLICATION_JSON; @@ -255,9 +629,9 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l case DATASOURCE_TSV: if(options & RRDR_OPTION_JSON_WRAP) { wb->content_type = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wrapper_begin(r, wb, format, options, true, group_method); rrdr2csv(r, wb, format, options, "", "\t", "\\n", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + wrapper_end(r, wb, format, options, true); } else { wb->content_type = CT_TEXT_PLAIN; @@ -268,11 +642,11 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l case DATASOURCE_HTML: if(options & RRDR_OPTION_JSON_WRAP) { wb->content_type = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wrapper_begin(r, wb, format, options, true, group_method); buffer_strcat(wb, "<html>\\n<center>\\n<table border=\\\"0\\\" cellpadding=\\\"5\\\" cellspacing=\\\"5\\\">\\n"); rrdr2csv(r, wb, format, options, "<tr><td>", "</td><td>", "</td></tr>\\n", ""); buffer_strcat(wb, "</table>\\n</center>\\n</html>\\n"); - rrdr_json_wrapper_end(r, wb, format, options, 1); + wrapper_end(r, wb, format, options, true); } else { wb->content_type = CT_TEXT_HTML; @@ -286,35 +660,35 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l wb->content_type = CT_APPLICATION_X_JAVASCRIPT; if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + wrapper_begin(r, wb, format, options, false, group_method); rrdr2json(r, wb, options, 1); if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb, format, options, false); break; case DATASOURCE_DATATABLE_JSON: wb->content_type = CT_APPLICATION_JSON; if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + wrapper_begin(r, wb, format, options, false, group_method); rrdr2json(r, wb, options, 1); if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb, format, options, false); break; case DATASOURCE_JSONP: wb->content_type = CT_APPLICATION_X_JAVASCRIPT; if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + wrapper_begin(r, wb, format, options, false, group_method); rrdr2json(r, wb, options, 0); if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb, format, options, false); break; case DATASOURCE_JSON: @@ -322,20 +696,27 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l wb->content_type = CT_APPLICATION_JSON; if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + wrapper_begin(r, wb, format, options, false, group_method); rrdr2json(r, wb, options, 0); if(options & RRDR_OPTION_JSON_WRAP) { + if(qt->request.group_by_aggregate_function == RRDR_GROUP_BY_FUNCTION_SUM_COUNT) { + rrdr_json_wrapper_group_by_count(r, wb, format, options, 0); + rrdr2json(r, wb, options | RRDR_OPTION_INTERNAL_GBC, 0); + } if(options & RRDR_OPTION_RETURN_JWAR) { rrdr_json_wrapper_anomaly_rates(r, wb, format, options, 0); rrdr2json(r, wb, options | RRDR_OPTION_INTERNAL_AR, 0); } - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb, format, options, false); } break; } - rrdr_free(owa, r); + if(r != r1) + rrdr_free(owa, r); + + rrdr_free(owa, r1); return HTTP_RESP_OK; } |