master
c 328 lines 12.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "api_v2_calls.h"
4
5 #define GROUP_BY_KEY_MAX_LENGTH 30
6 static struct {
7 char group_by[GROUP_BY_KEY_MAX_LENGTH + 1];
8 char aggregation[GROUP_BY_KEY_MAX_LENGTH + 1];
9 char group_by_label[GROUP_BY_KEY_MAX_LENGTH + 1];
10 } group_by_keys[MAX_QUERY_GROUP_BY_PASSES];
11
12 __attribute__((constructor)) void initialize_group_by_keys(void) {
13 for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
14 snprintfz(group_by_keys[g].group_by, GROUP_BY_KEY_MAX_LENGTH, "group_by[%zu]", g);
15 snprintfz(group_by_keys[g].aggregation, GROUP_BY_KEY_MAX_LENGTH, "aggregation[%zu]", g);
16 snprintfz(group_by_keys[g].group_by_label, GROUP_BY_KEY_MAX_LENGTH, "group_by_label[%zu]", g);
17 }
18 }
19
20 static int api_v23_data_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, size_t version) {
21 usec_t received_ut = now_monotonic_usec();
22
23 int ret = HTTP_RESP_BAD_REQUEST;
24
25 buffer_flush(w->response.data);
26
27 char *google_version = "0.6",
28 *google_reqId = "0",
29 *google_sig = "0",
30 *google_out = "json",
31 *responseHandler = NULL,
32 *outFileName = NULL;
33
34 time_t last_timestamp_in_data = 0, google_timestamp = 0;
35
36 char *scope_nodes = NULL;
37 char *scope_contexts = NULL;
38 char *scope_instances = NULL;
39 char *scope_labels = NULL;
40 char *scope_dimensions = NULL;
41 char *nodes = NULL;
42 char *contexts = NULL;
43 char *instances = NULL;
44 char *dimensions = NULL;
45 char *before_str = NULL;
46 char *after_str = NULL;
47 char *resampling_time_str = NULL;
48 char *points_str = NULL;
49 char *timeout_str = NULL;
50 char *labels = NULL;
51 char *alerts = NULL;
52 char *time_group_options = NULL;
53 char *tier_str = NULL;
54 char *cardinality_limit_str = NULL;
55 size_t tier = 0;
56 size_t cardinality_limit = 0;
57 RRDR_TIME_GROUPING time_group = RRDR_GROUPING_AVERAGE;
58 DATASOURCE_FORMAT format = DATASOURCE_JSON2;
59 RRDR_OPTIONS options = RRDR_OPTION_VIRTUAL_POINTS | RRDR_OPTION_JSON_WRAP | RRDR_OPTION_RETURN_JWAR;
60
61 struct group_by_pass group_by[MAX_QUERY_GROUP_BY_PASSES] = {
62 {
63 .group_by = RRDR_GROUP_BY_DIMENSION,
64 .group_by_label = NULL,
65 .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE,
66 },
67 };
68
69 size_t group_by_idx = 0, group_by_label_idx = 0, aggregation_idx = 0;
70
71 while(url) {
72 char *value = strsep_skip_consecutive_separators(&url, "&");
73 if(!value || !*value) continue;
74
75 char *name = strsep_skip_consecutive_separators(&value, "=");
76 if(!name || !*name) continue;
77 if(!value || !*value) continue;
78
79 // name and value are now the parameters
80 // they are not null and not empty
81
82 if(!strcmp(name, "scope_nodes")) scope_nodes = value;
83 else if(!strcmp(name, "scope_contexts")) scope_contexts = value;
84 else if(!strcmp(name, "scope_instances")) scope_instances = value;
85 else if(!strcmp(name, "scope_labels")) scope_labels = value;
86 else if(!strcmp(name, "scope_dimensions")) scope_dimensions = value;
87 else if(!strcmp(name, "nodes")) nodes = value;
88 else if(!strcmp(name, "contexts")) contexts = value;
89 else if(!strcmp(name, "instances")) instances = value;
90 else if(!strcmp(name, "dimensions")) dimensions = value;
91 else if(!strcmp(name, "labels")) labels = value;
92 else if(!strcmp(name, "alerts")) alerts = value;
93 else if(!strcmp(name, "after")) after_str = value;
94 else if(!strcmp(name, "before")) before_str = value;
95 else if(!strcmp(name, "points")) points_str = value;
96 else if(!strcmp(name, "timeout")) timeout_str = value;
97 else if(!strcmp(name, "group_by")) {
98 group_by[group_by_idx++].group_by = group_by_parse(value);
99 if(group_by_idx >= MAX_QUERY_GROUP_BY_PASSES)
100 group_by_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
101 }
102 else if(!strcmp(name, "group_by_label")) {
103 group_by[group_by_label_idx++].group_by_label = value;
104 if(group_by_label_idx >= MAX_QUERY_GROUP_BY_PASSES)
105 group_by_label_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
106 }
107 else if(!strcmp(name, "aggregation")) {
108 group_by[aggregation_idx++].aggregation = group_by_aggregate_function_parse(value);
109 if(aggregation_idx >= MAX_QUERY_GROUP_BY_PASSES)
110 aggregation_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
111 }
112 else if(!strcmp(name, "format")) format = datasource_format_str_to_id(value);
113 else if(!strcmp(name, "options")) options |= rrdr_options_parse(value);
114 else if(!strcmp(name, "time_group")) time_group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
115 else if(!strcmp(name, "time_group_options")) time_group_options = value;
116 else if(!strcmp(name, "time_resampling")) resampling_time_str = value;
117 else if(!strcmp(name, "tier")) tier_str = value;
118 else if(!strcmp(name, "cardinality_limit")) cardinality_limit_str = value;
119 else if(!strcmp(name, "callback")) responseHandler = value;
120 else if(!strcmp(name, "filename")) outFileName = value;
121 else if(!strcmp(name, "tqx")) {
122 // parse Google Visualization API options
123 // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
124 char *tqx_name, *tqx_value;
125
126 while(value) {
127 tqx_value = strsep_skip_consecutive_separators(&value, ";");
128 if(!tqx_value || !*tqx_value) continue;
129
130 tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
131 if(!tqx_name || !*tqx_name) continue;
132 if(!tqx_value || !*tqx_value) continue;
133
134 if(!strcmp(tqx_name, "version"))
135 google_version = tqx_value;
136 else if(!strcmp(tqx_name, "reqId"))
137 google_reqId = tqx_value;
138 else if(!strcmp(tqx_name, "sig")) {
139 google_sig = tqx_value;
140 google_timestamp = strtoul(google_sig, NULL, 0);
141 }
142 else if(!strcmp(tqx_name, "out")) {
143 google_out = tqx_value;
144 format = google_data_format_str_to_id(google_out);
145 }
146 else if(!strcmp(tqx_name, "responseHandler"))
147 responseHandler = tqx_value;
148 else if(!strcmp(tqx_name, "outFileName"))
149 outFileName = tqx_value;
150 }
151 }
152 else {
153 for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
154 if(!strcmp(name, group_by_keys[g].group_by))
155 group_by[g].group_by = group_by_parse(value);
156 else if(!strcmp(name, group_by_keys[g].group_by_label))
157 group_by[g].group_by_label = value;
158 else if(!strcmp(name, group_by_keys[g].aggregation))
159 group_by[g].aggregation = group_by_aggregate_function_parse(value);
160 }
161 }
162 }
163
164 // validate the google parameters given
165 fix_google_param(google_out);
166 fix_google_param(google_sig);
167 fix_google_param(google_reqId);
168 fix_google_param(google_version);
169 fix_google_param(responseHandler);
170 fix_google_param(outFileName);
171
172 for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
173 if (group_by[g].group_by_label && *group_by[g].group_by_label)
174 group_by[g].group_by |= RRDR_GROUP_BY_LABEL;
175 }
176
177 if(group_by[0].group_by == RRDR_GROUP_BY_NONE)
178 group_by[0].group_by = RRDR_GROUP_BY_DIMENSION;
179
180 for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
181 if ((group_by[g].group_by != RRDR_GROUP_BY_NONE && !(group_by[g].group_by & RRDR_GROUP_BY_DIMENSION)) ||
182 (options & RRDR_OPTION_PERCENTAGE)) {
183 options |= RRDR_OPTION_ABSOLUTE;
184 break;
185 }
186 }
187
188 if(options & RRDR_OPTION_DEBUG)
189 options &= ~RRDR_OPTION_MINIFY;
190
191 if(tier_str && *tier_str) {
192 tier = str2ul(tier_str);
193 if(tier < nd_profile.storage_tiers)
194 options |= RRDR_OPTION_SELECTED_TIER;
195 else
196 tier = 0;
197 }
198
199 if(cardinality_limit_str && *cardinality_limit_str) {
200 cardinality_limit = str2ul(cardinality_limit_str);
201 }
202
203 time_t before = (before_str && *before_str)?str2l(before_str):0;
204 time_t after = (after_str && *after_str) ?str2l(after_str):-600;
205 size_t points = (points_str && *points_str)?str2u(points_str):0;
206 int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
207 time_t resampling_time = (resampling_time_str && *resampling_time_str) ? str2l(resampling_time_str) : 0;
208
209 QUERY_TARGET_REQUEST qtr = {
210 .version = version,
211 .scope_nodes = scope_nodes,
212 .scope_contexts = scope_contexts,
213 .scope_instances = scope_instances,
214 .scope_labels = scope_labels,
215 .scope_dimensions = scope_dimensions,
216 .after = after,
217 .before = before,
218 .host = NULL,
219 .st = NULL,
220 .nodes = nodes,
221 .contexts = contexts,
222 .instances = instances,
223 .dimensions = dimensions,
224 .alerts = alerts,
225 .timeout_ms = timeout,
226 .points = points,
227 .format = format,
228 .options = options,
229 .time_group_method = time_group,
230 .time_group_options = time_group_options,
231 .resampling_time = resampling_time,
232 .tier = tier,
233 .chart_label_key = NULL,
234 .labels = labels,
235 .query_source = QUERY_SOURCE_API_DATA,
236 .priority = STORAGE_PRIORITY_NORMAL,
237 .received_ut = received_ut,
238 .cardinality_limit = cardinality_limit,
239
240 .interrupt_callback = web_client_interrupt_callback,
241 .interrupt_callback_data = w,
242
243 .transaction = &w->transaction,
244 };
245
246 for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++)
247 qtr.group_by[g] = group_by[g];
248
249 QUERY_TARGET *qt = query_target_create(&qtr);
250 ONEWAYALLOC *owa = NULL;
251
252 if(!qt) {
253 buffer_sprintf(w->response.data, "Failed to prepare the query.");
254 ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
255 goto cleanup;
256 }
257
258 web_client_timeout_checkpoint_set(w, timeout);
259 if(web_client_timeout_checkpoint_and_check(w, NULL)) {
260 ret = w->response.code;
261 goto cleanup;
262 }
263
264 if(outFileName && *outFileName) {
265 buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
266 netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
267 }
268
269 if(format == DATASOURCE_DATATABLE_JSONP) {
270 if(responseHandler == NULL)
271 responseHandler = "google.visualization.Query.setResponse";
272
273 netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
274 w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
275 );
276
277 buffer_sprintf(
278 w->response.data,
279 "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
280 responseHandler,
281 google_version,
282 google_reqId,
283 (int64_t)now_realtime_sec());
284 }
285 else if(format == DATASOURCE_JSONP) {
286 if(responseHandler == NULL)
287 responseHandler = "callback";
288
289 buffer_strcat(w->response.data, responseHandler);
290 buffer_strcat(w->response.data, "(");
291 }
292
293 owa = onewayalloc_create(0);
294 ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
295
296 if(format == DATASOURCE_DATATABLE_JSONP) {
297 if(google_timestamp < last_timestamp_in_data)
298 buffer_strcat(w->response.data, "});");
299
300 else {
301 // the client already has the latest data
302 buffer_flush(w->response.data);
303 buffer_sprintf(w->response.data,
304 "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
305 responseHandler, google_version, google_reqId);
306 }
307 }
308 else if(format == DATASOURCE_JSONP)
309 buffer_strcat(w->response.data, ");");
310
311 if(qt->internal.relative)
312 buffer_no_cacheable(w->response.data);
313 else
314 buffer_cacheable(w->response.data);
315
316 cleanup:
317 query_target_release(qt);
318 onewayalloc_destroy(owa);
319 return ret;
320 }
321
322 int api_v2_data(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
323 return api_v23_data_internal(host, w, url, 2);
324 }
325
326 int api_v3_data(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
327 return api_v23_data_internal(host, w, url, 3);
328 }