mirror of
https://github.com/netdata/netdata.git
synced 2025-05-04 09:10:31 +00:00

* expose the order of group by * key renames in json wrapper v2 * added group by context and group by units * added view_average_values * fix for view_average_values when percentage is specified * option group-by-labels is enabling the exposure of all the labels that are used for each of the final grouped dimensions * when executing group by queries, allocate one dimension data at a time - not all of them * respect hidden dimensions * cancel running data query on socket error * use poll to detect socket errors * use POLLRDHUP to detect half closed connections * make sure POLLRDHUP is available * do not destroy aral-by-size arals * completed documentation of /api/v2/data. * moved min, max back to view; updated swagger yaml and json * default format for /api/v2/data is json2
403 lines
12 KiB
C
403 lines
12 KiB
C
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "web/api/web_api_v1.h"
|
|
#include "database/storage_engine.h"
|
|
|
|
inline bool query_target_has_percentage_units(struct query_target *qt) {
|
|
if(qt->request.options & RRDR_OPTION_PERCENTAGE ||
|
|
qt->request.time_group_method == RRDR_GROUPING_CV)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool query_target_aggregatable(struct query_target *qt) {
|
|
return (qt->request.options & RRDR_OPTION_RETURN_RAW);
|
|
}
|
|
|
|
void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb) {
|
|
rrdset2json(st, wb, NULL, NULL, 0);
|
|
}
|
|
|
|
const char *rrdr_format_to_string(DATASOURCE_FORMAT format) {
|
|
switch(format) {
|
|
case DATASOURCE_JSON:
|
|
return DATASOURCE_FORMAT_JSON;
|
|
|
|
case DATASOURCE_JSON2:
|
|
return DATASOURCE_FORMAT_JSON2;
|
|
|
|
case DATASOURCE_DATATABLE_JSON:
|
|
return DATASOURCE_FORMAT_DATATABLE_JSON;
|
|
|
|
case DATASOURCE_DATATABLE_JSONP:
|
|
return DATASOURCE_FORMAT_DATATABLE_JSONP;
|
|
|
|
case DATASOURCE_JSONP:
|
|
return DATASOURCE_FORMAT_JSONP;
|
|
|
|
case DATASOURCE_SSV:
|
|
return DATASOURCE_FORMAT_SSV;
|
|
|
|
case DATASOURCE_CSV:
|
|
return DATASOURCE_FORMAT_CSV;
|
|
|
|
case DATASOURCE_TSV:
|
|
return DATASOURCE_FORMAT_TSV;
|
|
|
|
case DATASOURCE_HTML:
|
|
return DATASOURCE_FORMAT_HTML;
|
|
|
|
case DATASOURCE_JS_ARRAY:
|
|
return DATASOURCE_FORMAT_JS_ARRAY;
|
|
|
|
case DATASOURCE_SSV_COMMA:
|
|
return DATASOURCE_FORMAT_SSV_COMMA;
|
|
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
int rrdset2value_api_v1(
|
|
RRDSET *st
|
|
, BUFFER *wb
|
|
, NETDATA_DOUBLE *n
|
|
, const char *dimensions
|
|
, size_t points
|
|
, time_t after
|
|
, time_t before
|
|
, RRDR_TIME_GROUPING group_method
|
|
, const char *group_options
|
|
, time_t resampling_time
|
|
, uint32_t options
|
|
, time_t *db_after
|
|
, time_t *db_before
|
|
, size_t *db_points_read
|
|
, size_t *db_points_per_tier
|
|
, size_t *result_points_generated
|
|
, int *value_is_null
|
|
, NETDATA_DOUBLE *anomaly_rate
|
|
, time_t timeout
|
|
, size_t tier
|
|
, QUERY_SOURCE query_source
|
|
, STORAGE_PRIORITY priority
|
|
) {
|
|
int ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
|
|
|
|
ONEWAYALLOC *owa = onewayalloc_create(0);
|
|
RRDR *r = rrd2rrdr_legacy(
|
|
owa,
|
|
st,
|
|
points,
|
|
after,
|
|
before,
|
|
group_method,
|
|
resampling_time,
|
|
options,
|
|
dimensions,
|
|
group_options,
|
|
timeout,
|
|
tier,
|
|
query_source,
|
|
priority);
|
|
|
|
if(!r) {
|
|
if(value_is_null) *value_is_null = 1;
|
|
ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
if(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.qt->db.tiers[t].points;
|
|
}
|
|
|
|
if(result_points_generated)
|
|
*result_points_generated += r->stats.result_points_generated;
|
|
|
|
if(rrdr_rows(r) == 0) {
|
|
if(db_after) *db_after = 0;
|
|
if(db_before) *db_before = 0;
|
|
if(value_is_null) *value_is_null = 1;
|
|
|
|
ret = HTTP_RESP_BAD_REQUEST;
|
|
goto cleanup;
|
|
}
|
|
|
|
if(wb) {
|
|
if (r->view.flags & RRDR_RESULT_FLAG_RELATIVE)
|
|
buffer_no_cacheable(wb);
|
|
else if (r->view.flags & RRDR_RESULT_FLAG_ABSOLUTE)
|
|
buffer_cacheable(wb);
|
|
}
|
|
|
|
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);
|
|
ret = HTTP_RESP_OK;
|
|
|
|
cleanup:
|
|
rrdr_free(owa, r);
|
|
onewayalloc_destroy(owa);
|
|
return ret;
|
|
}
|
|
|
|
static inline void buffer_json_member_add_key_only(BUFFER *wb, const char *key) {
|
|
buffer_print_json_comma_newline_spacing(wb);
|
|
buffer_print_json_key(wb, key);
|
|
buffer_fast_strcat(wb, ":", 1);
|
|
wb->json.stack[wb->json.depth].count++;
|
|
}
|
|
|
|
static inline void buffer_json_member_add_string_open(BUFFER *wb, const char *key) {
|
|
buffer_json_member_add_key_only(wb, key);
|
|
buffer_strcat(wb, wb->json.value_quote);
|
|
}
|
|
|
|
static inline void buffer_json_member_add_string_close(BUFFER *wb) {
|
|
buffer_strcat(wb, wb->json.value_quote);
|
|
}
|
|
|
|
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;
|
|
|
|
if(qt->request.version == 2) {
|
|
wrapper_begin = rrdr_json_wrapper_begin2;
|
|
wrapper_end = rrdr_json_wrapper_end2;
|
|
}
|
|
|
|
RRDR *r = rrd2rrdr(owa, qt);
|
|
qt->timings.executed_ut = now_monotonic_usec();
|
|
|
|
if(!r) {
|
|
buffer_strcat(wb, "Cannot generate output with these parameters on this chart.");
|
|
return HTTP_RESP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
if (r->view.flags & RRDR_RESULT_FLAG_CANCEL) {
|
|
rrdr_free(owa, r);
|
|
return HTTP_RESP_BACKEND_FETCH_FAILED;
|
|
}
|
|
|
|
if(r->view.flags & RRDR_RESULT_FLAG_RELATIVE)
|
|
buffer_no_cacheable(wb);
|
|
else if(r->view.flags & RRDR_RESULT_FLAG_ABSOLUTE)
|
|
buffer_cacheable(wb);
|
|
|
|
if(latest_timestamp && rrdr_rows(r) > 0)
|
|
*latest_timestamp = r->view.before;
|
|
|
|
DATASOURCE_FORMAT format = qt->request.format;
|
|
RRDR_OPTIONS options = qt->request.options;
|
|
|
|
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;
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_string_open(wb, "result");
|
|
rrdr2ssv(r, wb, options, "", " ", "");
|
|
buffer_json_member_add_string_close(wb);
|
|
wrapper_end(r, wb);
|
|
}
|
|
else {
|
|
wb->content_type = CT_TEXT_PLAIN;
|
|
rrdr2ssv(r, wb, options, "", " ", "");
|
|
}
|
|
break;
|
|
|
|
case DATASOURCE_SSV_COMMA:
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_string_open(wb, "result");
|
|
rrdr2ssv(r, wb, options, "", ",", "");
|
|
buffer_json_member_add_string_close(wb);
|
|
wrapper_end(r, wb);
|
|
}
|
|
else {
|
|
wb->content_type = CT_TEXT_PLAIN;
|
|
rrdr2ssv(r, wb, options, "", ",", "");
|
|
}
|
|
break;
|
|
|
|
case DATASOURCE_JS_ARRAY:
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_array(wb, "result");
|
|
rrdr2ssv(r, wb, options, "", ",", "");
|
|
buffer_json_array_close(wb);
|
|
wrapper_end(r, wb);
|
|
}
|
|
else {
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
rrdr2ssv(r, wb, options, "[", ",", "]");
|
|
}
|
|
break;
|
|
|
|
case DATASOURCE_CSV:
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_string_open(wb, "result");
|
|
rrdr2csv(r, wb, format, options, "", ",", "\\n", "");
|
|
buffer_json_member_add_string_close(wb);
|
|
wrapper_end(r, wb);
|
|
}
|
|
else {
|
|
wb->content_type = CT_TEXT_PLAIN;
|
|
rrdr2csv(r, wb, format, options, "", ",", "\r\n", "");
|
|
}
|
|
break;
|
|
|
|
case DATASOURCE_CSV_MARKDOWN:
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_string_open(wb, "result");
|
|
rrdr2csv(r, wb, format, options, "", "|", "\\n", "");
|
|
buffer_json_member_add_string_close(wb);
|
|
wrapper_end(r, wb);
|
|
}
|
|
else {
|
|
wb->content_type = CT_TEXT_PLAIN;
|
|
rrdr2csv(r, wb, format, options, "", "|", "\r\n", "");
|
|
}
|
|
break;
|
|
|
|
case DATASOURCE_CSV_JSON_ARRAY:
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_array(wb, "result");
|
|
rrdr2csv(r, wb, format, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n");
|
|
buffer_json_array_close(wb);
|
|
wrapper_end(r, wb);
|
|
}
|
|
else {
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
buffer_strcat(wb, "[\n");
|
|
rrdr2csv(r, wb, format, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n");
|
|
buffer_strcat(wb, "\n]");
|
|
}
|
|
break;
|
|
|
|
case DATASOURCE_TSV:
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_string_open(wb, "result");
|
|
rrdr2csv(r, wb, format, options, "", "\t", "\\n", "");
|
|
buffer_json_member_add_string_close(wb);
|
|
wrapper_end(r, wb);
|
|
}
|
|
else {
|
|
wb->content_type = CT_TEXT_PLAIN;
|
|
rrdr2csv(r, wb, format, options, "", "\t", "\r\n", "");
|
|
}
|
|
break;
|
|
|
|
case DATASOURCE_HTML:
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_string_open(wb, "result");
|
|
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");
|
|
buffer_json_member_add_string_close(wb);
|
|
wrapper_end(r, wb);
|
|
}
|
|
else {
|
|
wb->content_type = CT_TEXT_HTML;
|
|
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");
|
|
}
|
|
break;
|
|
|
|
case DATASOURCE_DATATABLE_JSONP:
|
|
wb->content_type = CT_APPLICATION_X_JAVASCRIPT;
|
|
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_key_only(wb, "result");
|
|
}
|
|
|
|
rrdr2json(r, wb, options, 1);
|
|
|
|
if(options & RRDR_OPTION_JSON_WRAP)
|
|
wrapper_end(r, wb);
|
|
|
|
break;
|
|
|
|
case DATASOURCE_DATATABLE_JSON:
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_key_only(wb, "result");
|
|
}
|
|
|
|
rrdr2json(r, wb, options, 1);
|
|
|
|
if(options & RRDR_OPTION_JSON_WRAP)
|
|
wrapper_end(r, wb);
|
|
|
|
break;
|
|
|
|
case DATASOURCE_JSONP:
|
|
wb->content_type = CT_APPLICATION_X_JAVASCRIPT;
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_key_only(wb, "result");
|
|
}
|
|
|
|
rrdr2json(r, wb, options, 0);
|
|
|
|
if(options & RRDR_OPTION_JSON_WRAP)
|
|
wrapper_end(r, wb);
|
|
|
|
break;
|
|
|
|
case DATASOURCE_JSON:
|
|
default:
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
wrapper_begin(r, wb);
|
|
buffer_json_member_add_key_only(wb, "result");
|
|
}
|
|
|
|
rrdr2json(r, wb, options, 0);
|
|
|
|
if(options & RRDR_OPTION_JSON_WRAP) {
|
|
if (options & RRDR_OPTION_RETURN_JWAR) {
|
|
buffer_json_member_add_key_only(wb, "anomaly_rates");
|
|
rrdr2json(r, wb, options | RRDR_OPTION_INTERNAL_AR, false);
|
|
}
|
|
wrapper_end(r, wb);
|
|
}
|
|
break;
|
|
|
|
case DATASOURCE_JSON2:
|
|
wb->content_type = CT_APPLICATION_JSON;
|
|
wrapper_begin(r, wb);
|
|
rrdr2json_v2(r, wb);
|
|
wrapper_end(r, wb);
|
|
break;
|
|
}
|
|
|
|
rrdr_free(owa, r);
|
|
return HTTP_RESP_OK;
|
|
}
|