master
c 253 lines 9.35 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "api_v1_calls.h"
4
5 int api_v1_data(RRDHOST *host, struct web_client *w, char *url) {
6 netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
7
8 int ret = HTTP_RESP_BAD_REQUEST;
9 BUFFER *dimensions = NULL;
10
11 buffer_flush(w->response.data);
12
13 char *google_version = "0.6",
14 *google_reqId = "0",
15 *google_sig = "0",
16 *google_out = "json",
17 *responseHandler = NULL,
18 *outFileName = NULL;
19
20 time_t last_timestamp_in_data = 0, google_timestamp = 0;
21
22 char *chart = NULL;
23 char *before_str = NULL;
24 char *after_str = NULL;
25 char *group_time_str = NULL;
26 char *points_str = NULL;
27 char *timeout_str = NULL;
28 char *context = NULL;
29 char *chart_label_key = NULL;
30 char *chart_labels_filter = NULL;
31 char *group_options = NULL;
32 char *cardinality_limit_str = NULL;
33 size_t tier = 0;
34 size_t cardinality_limit = 0;
35 RRDR_TIME_GROUPING group = RRDR_GROUPING_AVERAGE;
36 DATASOURCE_FORMAT format = DATASOURCE_JSON;
37 RRDR_OPTIONS options = 0;
38
39 while(url) {
40 char *value = strsep_skip_consecutive_separators(&url, "&");
41 if(!value || !*value) continue;
42
43 char *name = strsep_skip_consecutive_separators(&value, "=");
44 if(!name || !*name) continue;
45 if(!value || !*value) continue;
46
47 netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
48
49 // name and value are now the parameters
50 // they are not null and not empty
51
52 if(!strcmp(name, "context")) context = value;
53 else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
54 else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
55 else if(!strcmp(name, "chart")) chart = value;
56 else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
57 if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
58 buffer_strcat(dimensions, "|");
59 buffer_strcat(dimensions, value);
60 }
61 else if(!strcmp(name, "show_dimensions")) options |= RRDR_OPTION_ALL_DIMENSIONS;
62 else if(!strcmp(name, "after")) after_str = value;
63 else if(!strcmp(name, "before")) before_str = value;
64 else if(!strcmp(name, "points")) points_str = value;
65 else if(!strcmp(name, "timeout")) timeout_str = value;
66 else if(!strcmp(name, "gtime")) group_time_str = value;
67 else if(!strcmp(name, "group_options")) group_options = value;
68 else if(!strcmp(name, "cardinality_limit")) cardinality_limit_str = value;
69 else if(!strcmp(name, "group")) {
70 group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
71 }
72 else if(!strcmp(name, "format")) {
73 format = datasource_format_str_to_id(value);
74 }
75 else if(!strcmp(name, "options")) {
76 options |= rrdr_options_parse(value);
77 }
78 else if(!strcmp(name, "callback")) {
79 responseHandler = value;
80 }
81 else if(!strcmp(name, "filename")) {
82 outFileName = value;
83 }
84 else if(!strcmp(name, "tqx")) {
85 // parse Google Visualization API options
86 // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
87 char *tqx_name, *tqx_value;
88
89 while(value) {
90 tqx_value = strsep_skip_consecutive_separators(&value, ";");
91 if(!tqx_value || !*tqx_value) continue;
92
93 tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
94 if(!tqx_name || !*tqx_name) continue;
95 if(!tqx_value || !*tqx_value) continue;
96
97 if(!strcmp(tqx_name, "version"))
98 google_version = tqx_value;
99 else if(!strcmp(tqx_name, "reqId"))
100 google_reqId = tqx_value;
101 else if(!strcmp(tqx_name, "sig")) {
102 google_sig = tqx_value;
103 google_timestamp = strtoul(google_sig, NULL, 0);
104 }
105 else if(!strcmp(tqx_name, "out")) {
106 google_out = tqx_value;
107 format = google_data_format_str_to_id(google_out);
108 }
109 else if(!strcmp(tqx_name, "responseHandler"))
110 responseHandler = tqx_value;
111 else if(!strcmp(tqx_name, "outFileName"))
112 outFileName = tqx_value;
113 }
114 }
115 else if(!strcmp(name, "tier")) {
116 tier = str2ul(value);
117 if(tier < nd_profile.storage_tiers)
118 options |= RRDR_OPTION_SELECTED_TIER;
119 else
120 tier = 0;
121 }
122 }
123
124 // validate the google parameters given
125 fix_google_param(google_out);
126 fix_google_param(google_sig);
127 fix_google_param(google_reqId);
128 fix_google_param(google_version);
129 fix_google_param(responseHandler);
130 fix_google_param(outFileName);
131
132 RRDSET *st = NULL;
133 ONEWAYALLOC *owa = onewayalloc_create(0);
134 QUERY_TARGET *qt = NULL;
135
136 if(!is_valid_sp(chart) && !is_valid_sp(context)) {
137 buffer_sprintf(w->response.data, "No chart or context is given.");
138 goto cleanup;
139 }
140
141 if(chart && !context) {
142 // check if this is a specific chart
143 st = rrdset_find(host, chart, false);
144 if (!st) st = rrdset_find_byname(host, chart);
145 }
146
147 long long before = (before_str && *before_str)?str2l(before_str):0;
148 long long after = (after_str && *after_str) ?str2l(after_str):-600;
149 int points = (points_str && *points_str)?str2i(points_str):0;
150 int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
151 long group_time = (group_time_str && *group_time_str)?str2l(group_time_str):0;
152
153 if (cardinality_limit_str && *cardinality_limit_str)
154 cardinality_limit = str2ul(cardinality_limit_str);
155
156 QUERY_TARGET_REQUEST qtr = {
157 .version = 1,
158 .after = after,
159 .before = before,
160 .host = host,
161 .st = st,
162 .nodes = NULL,
163 .contexts = context,
164 .instances = chart,
165 .dimensions = (dimensions)?buffer_tostring(dimensions):NULL,
166 .timeout_ms = timeout,
167 .points = points,
168 .format = format,
169 .options = options,
170 .time_group_method = group,
171 .time_group_options = group_options,
172 .resampling_time = group_time,
173 .tier = tier,
174 .chart_label_key = chart_label_key,
175 .labels = chart_labels_filter,
176 .query_source = QUERY_SOURCE_API_DATA,
177 .priority = STORAGE_PRIORITY_NORMAL,
178 .cardinality_limit = cardinality_limit,
179 .interrupt_callback = web_client_interrupt_callback,
180 .interrupt_callback_data = w,
181 .transaction = &w->transaction,
182 };
183 qt = query_target_create(&qtr);
184
185 if(!qt || !qt->query.used) {
186 buffer_sprintf(w->response.data, "No metrics where matched to query.");
187 ret = HTTP_RESP_NOT_FOUND;
188 goto cleanup;
189 }
190
191 web_client_timeout_checkpoint_set(w, timeout);
192 if(web_client_timeout_checkpoint_and_check(w, NULL)) {
193 ret = w->response.code;
194 goto cleanup;
195 }
196
197 if(outFileName && *outFileName) {
198 buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
199 netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
200 }
201
202 if(format == DATASOURCE_DATATABLE_JSONP) {
203 if(responseHandler == NULL)
204 responseHandler = "google.visualization.Query.setResponse";
205
206 netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
207 w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
208 );
209
210 buffer_sprintf(
211 w->response.data,
212 "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
213 responseHandler,
214 google_version,
215 google_reqId,
216 (int64_t)(st ? st->last_updated.tv_sec : 0));
217 }
218 else if(format == DATASOURCE_JSONP) {
219 if(responseHandler == NULL)
220 responseHandler = "callback";
221
222 buffer_strcat(w->response.data, responseHandler);
223 buffer_strcat(w->response.data, "(");
224 }
225
226 ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
227
228 if(format == DATASOURCE_DATATABLE_JSONP) {
229 if(google_timestamp < last_timestamp_in_data)
230 buffer_strcat(w->response.data, "});");
231
232 else {
233 // the client already has the latest data
234 buffer_flush(w->response.data);
235 buffer_sprintf(w->response.data,
236 "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
237 responseHandler, google_version, google_reqId);
238 }
239 }
240 else if(format == DATASOURCE_JSONP)
241 buffer_strcat(w->response.data, ");");
242
243 if(qt->internal.relative)
244 buffer_no_cacheable(w->response.data);
245 else
246 buffer_cacheable(w->response.data);
247
248 cleanup:
249 query_target_release(qt);
250 onewayalloc_destroy(owa);
251 buffer_free(dimensions);
252 return ret;
253 }