]> arthur.barton.de Git - netdata.git/blob - src/web_api_v1.c
self-cleaning obsolete cgroups and network interfaces from memory; fixes #1163; fixes...
[netdata.git] / src / web_api_v1.c
1 #include "common.h"
2
3 inline int web_client_api_request_v1_data_group(char *name, int def) {
4     if(!strcmp(name, "average"))
5         return GROUP_AVERAGE;
6
7     else if(!strcmp(name, "min"))
8         return GROUP_MIN;
9
10     else if(!strcmp(name, "max"))
11         return GROUP_MAX;
12
13     else if(!strcmp(name, "sum"))
14         return GROUP_SUM;
15
16     else if(!strcmp(name, "incremental-sum"))
17         return GROUP_INCREMENTAL_SUM;
18
19     return def;
20 }
21
22 inline uint32_t web_client_api_request_v1_data_options(char *o) {
23     uint32_t ret = 0x00000000;
24     char *tok;
25
26     while(o && *o && (tok = mystrsep(&o, ", |"))) {
27         if(!*tok) continue;
28
29         if(!strcmp(tok, "nonzero"))
30             ret |= RRDR_OPTION_NONZERO;
31         else if(!strcmp(tok, "flip") || !strcmp(tok, "reversed") || !strcmp(tok, "reverse"))
32             ret |= RRDR_OPTION_REVERSED;
33         else if(!strcmp(tok, "jsonwrap"))
34             ret |= RRDR_OPTION_JSON_WRAP;
35         else if(!strcmp(tok, "min2max"))
36             ret |= RRDR_OPTION_MIN2MAX;
37         else if(!strcmp(tok, "ms") || !strcmp(tok, "milliseconds"))
38             ret |= RRDR_OPTION_MILLISECONDS;
39         else if(!strcmp(tok, "abs") || !strcmp(tok, "absolute") || !strcmp(tok, "absolute_sum") || !strcmp(tok, "absolute-sum"))
40             ret |= RRDR_OPTION_ABSOLUTE;
41         else if(!strcmp(tok, "seconds"))
42             ret |= RRDR_OPTION_SECONDS;
43         else if(!strcmp(tok, "null2zero"))
44             ret |= RRDR_OPTION_NULL2ZERO;
45         else if(!strcmp(tok, "objectrows"))
46             ret |= RRDR_OPTION_OBJECTSROWS;
47         else if(!strcmp(tok, "google_json"))
48             ret |= RRDR_OPTION_GOOGLE_JSON;
49         else if(!strcmp(tok, "percentage"))
50             ret |= RRDR_OPTION_PERCENTAGE;
51         else if(!strcmp(tok, "unaligned"))
52             ret |= RRDR_OPTION_NOT_ALIGNED;
53     }
54
55     return ret;
56 }
57
58 inline uint32_t web_client_api_request_v1_data_format(char *name) {
59     if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSON)) // datatable
60         return DATASOURCE_DATATABLE_JSON;
61
62     else if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSONP)) // datasource
63         return DATASOURCE_DATATABLE_JSONP;
64
65     else if(!strcmp(name, DATASOURCE_FORMAT_JSON)) // json
66         return DATASOURCE_JSON;
67
68     else if(!strcmp(name, DATASOURCE_FORMAT_JSONP)) // jsonp
69         return DATASOURCE_JSONP;
70
71     else if(!strcmp(name, DATASOURCE_FORMAT_SSV)) // ssv
72         return DATASOURCE_SSV;
73
74     else if(!strcmp(name, DATASOURCE_FORMAT_CSV)) // csv
75         return DATASOURCE_CSV;
76
77     else if(!strcmp(name, DATASOURCE_FORMAT_TSV) || !strcmp(name, "tsv-excel")) // tsv
78         return DATASOURCE_TSV;
79
80     else if(!strcmp(name, DATASOURCE_FORMAT_HTML)) // html
81         return DATASOURCE_HTML;
82
83     else if(!strcmp(name, DATASOURCE_FORMAT_JS_ARRAY)) // array
84         return DATASOURCE_JS_ARRAY;
85
86     else if(!strcmp(name, DATASOURCE_FORMAT_SSV_COMMA)) // ssvcomma
87         return DATASOURCE_SSV_COMMA;
88
89     else if(!strcmp(name, DATASOURCE_FORMAT_CSV_JSON_ARRAY)) // csvjsonarray
90         return DATASOURCE_CSV_JSON_ARRAY;
91
92     return DATASOURCE_JSON;
93 }
94
95 inline uint32_t web_client_api_request_v1_data_google_format(char *name) {
96     if(!strcmp(name, "json"))
97         return DATASOURCE_DATATABLE_JSONP;
98
99     else if(!strcmp(name, "html"))
100         return DATASOURCE_HTML;
101
102     else if(!strcmp(name, "csv"))
103         return DATASOURCE_CSV;
104
105     else if(!strcmp(name, "tsv-excel"))
106         return DATASOURCE_TSV;
107
108     return DATASOURCE_JSON;
109 }
110
111
112 inline int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url) {
113     int all = 0;
114
115     while(url) {
116         char *value = mystrsep(&url, "?&");
117         if (!value || !*value) continue;
118
119         if(!strcmp(value, "all")) all = 1;
120         else if(!strcmp(value, "active")) all = 0;
121     }
122
123     buffer_flush(w->response.data);
124     w->response.data->contenttype = CT_APPLICATION_JSON;
125     health_alarms2json(host, w->response.data, all);
126     return 200;
127 }
128
129 inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url) {
130     uint32_t after = 0;
131
132     while(url) {
133         char *value = mystrsep(&url, "?&");
134         if (!value || !*value) continue;
135
136         char *name = mystrsep(&value, "=");
137         if(!name || !*name) continue;
138         if(!value || !*value) continue;
139
140         if(!strcmp(name, "after")) after = (uint32_t)strtoul(value, NULL, 0);
141     }
142
143     buffer_flush(w->response.data);
144     w->response.data->contenttype = CT_APPLICATION_JSON;
145     health_alarm_log2json(host, w->response.data, after);
146     return 200;
147 }
148
149 inline int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)) {
150     int ret = 400;
151     char *chart = NULL;
152
153     buffer_flush(w->response.data);
154
155     while(url) {
156         char *value = mystrsep(&url, "?&");
157         if(!value || !*value) continue;
158
159         char *name = mystrsep(&value, "=");
160         if(!name || !*name) continue;
161         if(!value || !*value) continue;
162
163         // name and value are now the parameters
164         // they are not null and not empty
165
166         if(!strcmp(name, "chart")) chart = value;
167         //else {
168         /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
169         //  goto cleanup;
170         //}
171     }
172
173     if(!chart || !*chart) {
174         buffer_sprintf(w->response.data, "No chart id is given at the request.");
175         goto cleanup;
176     }
177
178     RRDSET *st = rrdset_find(host, chart);
179     if(!st) st = rrdset_find_byname(host, chart);
180     if(!st) {
181         buffer_strcat(w->response.data, "Chart is not found: ");
182         buffer_strcat_htmlescape(w->response.data, chart);
183         ret = 404;
184         goto cleanup;
185     }
186
187     w->response.data->contenttype = CT_APPLICATION_JSON;
188     st->last_accessed_time = now_realtime_sec();
189     callback(st, w->response.data);
190     return 200;
191
192     cleanup:
193     return ret;
194 }
195
196 inline int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url) {
197     return web_client_api_request_single_chart(host, w, url, health_api_v1_chart_variables2json);
198 }
199
200 inline int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url) {
201     (void)url;
202
203     buffer_flush(w->response.data);
204     w->response.data->contenttype = CT_APPLICATION_JSON;
205     rrd_stats_api_v1_charts(host, w->response.data);
206     return 200;
207 }
208
209 inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url) {
210     int format = ALLMETRICS_SHELL;
211
212     while(url) {
213         char *value = mystrsep(&url, "?&");
214         if (!value || !*value) continue;
215
216         char *name = mystrsep(&value, "=");
217         if(!name || !*name) continue;
218         if(!value || !*value) continue;
219
220         if(!strcmp(name, "format")) {
221             if(!strcmp(value, ALLMETRICS_FORMAT_SHELL))
222                 format = ALLMETRICS_SHELL;
223             else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS))
224                 format = ALLMETRICS_PROMETHEUS;
225             else
226                 format = 0;
227         }
228     }
229
230     buffer_flush(w->response.data);
231     buffer_no_cacheable(w->response.data);
232
233     switch(format) {
234         case ALLMETRICS_SHELL:
235             w->response.data->contenttype = CT_TEXT_PLAIN;
236             rrd_stats_api_v1_charts_allmetrics_shell(host, w->response.data);
237             return 200;
238
239         case ALLMETRICS_PROMETHEUS:
240             w->response.data->contenttype = CT_PROMETHEUS;
241             rrd_stats_api_v1_charts_allmetrics_prometheus(host, w->response.data);
242             return 200;
243
244         default:
245             w->response.data->contenttype = CT_TEXT_PLAIN;
246             buffer_strcat(w->response.data, "Which format? Only '" ALLMETRICS_FORMAT_SHELL "' and '" ALLMETRICS_FORMAT_PROMETHEUS "' is currently supported.");
247             return 400;
248     }
249 }
250
251 inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url) {
252     return web_client_api_request_single_chart(host, w, url, rrd_stats_api_v1_chart);
253 }
254
255 int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
256     int ret = 400;
257     buffer_flush(w->response.data);
258
259     BUFFER *dimensions = NULL;
260
261     const char *chart = NULL
262     , *before_str = NULL
263     , *after_str = NULL
264     , *points_str = NULL
265     , *multiply_str = NULL
266     , *divide_str = NULL
267     , *label = NULL
268     , *units = NULL
269     , *label_color = NULL
270     , *value_color = NULL
271     , *refresh_str = NULL
272     , *precision_str = NULL
273     , *alarm = NULL;
274
275     int group = GROUP_AVERAGE;
276     uint32_t options = 0x00000000;
277
278     while(url) {
279         char *value = mystrsep(&url, "/?&");
280         if(!value || !*value) continue;
281
282         char *name = mystrsep(&value, "=");
283         if(!name || !*name) continue;
284         if(!value || !*value) continue;
285
286         debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value);
287
288         // name and value are now the parameters
289         // they are not null and not empty
290
291         if(!strcmp(name, "chart")) chart = value;
292         else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
293             if(!dimensions)
294                 dimensions = buffer_create(100);
295
296             buffer_strcat(dimensions, "|");
297             buffer_strcat(dimensions, value);
298         }
299         else if(!strcmp(name, "after")) after_str = value;
300         else if(!strcmp(name, "before")) before_str = value;
301         else if(!strcmp(name, "points")) points_str = value;
302         else if(!strcmp(name, "group")) {
303             group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE);
304         }
305         else if(!strcmp(name, "options")) {
306             options |= web_client_api_request_v1_data_options(value);
307         }
308         else if(!strcmp(name, "label")) label = value;
309         else if(!strcmp(name, "units")) units = value;
310         else if(!strcmp(name, "label_color")) label_color = value;
311         else if(!strcmp(name, "value_color")) value_color = value;
312         else if(!strcmp(name, "multiply")) multiply_str = value;
313         else if(!strcmp(name, "divide")) divide_str = value;
314         else if(!strcmp(name, "refresh")) refresh_str = value;
315         else if(!strcmp(name, "precision")) precision_str = value;
316         else if(!strcmp(name, "alarm")) alarm = value;
317     }
318
319     if(!chart || !*chart) {
320         buffer_no_cacheable(w->response.data);
321         buffer_sprintf(w->response.data, "No chart id is given at the request.");
322         goto cleanup;
323     }
324
325     RRDSET *st = rrdset_find(host, chart);
326     if(!st) st = rrdset_find_byname(host, chart);
327     if(!st) {
328         buffer_no_cacheable(w->response.data);
329         buffer_svg(w->response.data, "chart not found", NAN, "", NULL, NULL, -1);
330         ret = 200;
331         goto cleanup;
332     }
333     st->last_accessed_time = now_realtime_sec();
334
335     RRDCALC *rc = NULL;
336     if(alarm) {
337         rc = rrdcalc_find(st, alarm);
338         if (!rc) {
339             buffer_no_cacheable(w->response.data);
340             buffer_svg(w->response.data, "alarm not found", NAN, "", NULL, NULL, -1);
341             ret = 200;
342             goto cleanup;
343         }
344     }
345
346     long long multiply  = (multiply_str  && *multiply_str )?str2l(multiply_str):1;
347     long long divide    = (divide_str    && *divide_str   )?str2l(divide_str):1;
348     long long before    = (before_str    && *before_str   )?str2l(before_str):0;
349     long long after     = (after_str     && *after_str    )?str2l(after_str):-st->update_every;
350     int       points    = (points_str    && *points_str   )?str2i(points_str):1;
351     int       precision = (precision_str && *precision_str)?str2i(precision_str):-1;
352
353     if(!multiply) multiply = 1;
354     if(!divide) divide = 1;
355
356     int refresh = 0;
357     if(refresh_str && *refresh_str) {
358         if(!strcmp(refresh_str, "auto")) {
359             if(rc) refresh = rc->update_every;
360             else if(options & RRDR_OPTION_NOT_ALIGNED)
361                 refresh = st->update_every;
362             else {
363                 refresh = (int)(before - after);
364                 if(refresh < 0) refresh = -refresh;
365             }
366         }
367         else {
368             refresh = str2i(refresh_str);
369             if(refresh < 0) refresh = -refresh;
370         }
371     }
372
373     if(!label) {
374         if(alarm) {
375             char *s = (char *)alarm;
376             while(*s) {
377                 if(*s == '_') *s = ' ';
378                 s++;
379             }
380             label = alarm;
381         }
382         else if(dimensions) {
383             const char *dim = buffer_tostring(dimensions);
384             if(*dim == '|') dim++;
385             label = dim;
386         }
387         else
388             label = st->name;
389     }
390     if(!units) {
391         if(alarm) {
392             if(rc->units)
393                 units = rc->units;
394             else
395                 units = "";
396         }
397         else if(options & RRDR_OPTION_PERCENTAGE)
398             units = "%";
399         else
400             units = st->units;
401     }
402
403     debug(D_WEB_CLIENT, "%llu: API command 'badge.svg' for chart '%s', alarm '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', options '0x%08x'"
404           , w->id
405           , chart
406           , alarm?alarm:""
407           , (dimensions)?buffer_tostring(dimensions):""
408           , after
409           , before
410           , points
411           , group
412           , options
413     );
414
415     if(rc) {
416         if (refresh > 0) {
417             buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
418             w->response.data->expires = now_realtime_sec() + refresh;
419         }
420         else buffer_no_cacheable(w->response.data);
421
422         if(!value_color) {
423             switch(rc->status) {
424                 case RRDCALC_STATUS_CRITICAL:
425                     value_color = "red";
426                     break;
427
428                 case RRDCALC_STATUS_WARNING:
429                     value_color = "orange";
430                     break;
431
432                 case RRDCALC_STATUS_CLEAR:
433                     value_color = "brightgreen";
434                     break;
435
436                 case RRDCALC_STATUS_UNDEFINED:
437                     value_color = "lightgrey";
438                     break;
439
440                 case RRDCALC_STATUS_UNINITIALIZED:
441                     value_color = "#000";
442                     break;
443
444                 default:
445                     value_color = "grey";
446                     break;
447             }
448         }
449
450         buffer_svg(w->response.data,
451                 label,
452                 (isnan(rc->value)||isinf(rc->value)) ? rc->value : rc->value * multiply / divide,
453                 units,
454                 label_color,
455                 value_color,
456                 precision);
457         ret = 200;
458     }
459     else {
460         time_t latest_timestamp = 0;
461         int value_is_null = 1;
462         calculated_number n = NAN;
463         ret = 500;
464
465         // if the collected value is too old, don't calculate its value
466         if (rrdset_last_entry_t(st) >= (now_realtime_sec() - (st->update_every * st->gap_when_lost_iterations_above)))
467             ret = rrdset2value_api_v1(st, w->response.data, &n, (dimensions) ? buffer_tostring(dimensions) : NULL
468                                       , points, after, before, group, options, NULL, &latest_timestamp, &value_is_null);
469
470         // if the value cannot be calculated, show empty badge
471         if (ret != 200) {
472             buffer_no_cacheable(w->response.data);
473             value_is_null = 1;
474             n = 0;
475             ret = 200;
476         }
477         else if (refresh > 0) {
478             buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
479             w->response.data->expires = now_realtime_sec() + refresh;
480         }
481         else buffer_no_cacheable(w->response.data);
482
483         // render the badge
484         buffer_svg(w->response.data,
485                 label,
486                 (value_is_null)?NAN:(n * multiply / divide),
487                 units,
488                 label_color,
489                 value_color,
490                 precision);
491     }
492
493     cleanup:
494     buffer_free(dimensions);
495     return ret;
496 }
497
498 // returns the HTTP code
499 inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) {
500     debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
501
502     int ret = 400;
503     BUFFER *dimensions = NULL;
504
505     buffer_flush(w->response.data);
506
507     char    *google_version = "0.6",
508             *google_reqId = "0",
509             *google_sig = "0",
510             *google_out = "json",
511             *responseHandler = NULL,
512             *outFileName = NULL;
513
514     time_t last_timestamp_in_data = 0, google_timestamp = 0;
515
516     char *chart = NULL
517     , *before_str = NULL
518     , *after_str = NULL
519     , *points_str = NULL;
520
521     int group = GROUP_AVERAGE;
522     uint32_t format = DATASOURCE_JSON;
523     uint32_t options = 0x00000000;
524
525     while(url) {
526         char *value = mystrsep(&url, "?&");
527         if(!value || !*value) continue;
528
529         char *name = mystrsep(&value, "=");
530         if(!name || !*name) continue;
531         if(!value || !*value) continue;
532
533         debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
534
535         // name and value are now the parameters
536         // they are not null and not empty
537
538         if(!strcmp(name, "chart")) chart = value;
539         else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
540             if(!dimensions) dimensions = buffer_create(100);
541             buffer_strcat(dimensions, "|");
542             buffer_strcat(dimensions, value);
543         }
544         else if(!strcmp(name, "after")) after_str = value;
545         else if(!strcmp(name, "before")) before_str = value;
546         else if(!strcmp(name, "points")) points_str = value;
547         else if(!strcmp(name, "group")) {
548             group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE);
549         }
550         else if(!strcmp(name, "format")) {
551             format = web_client_api_request_v1_data_format(value);
552         }
553         else if(!strcmp(name, "options")) {
554             options |= web_client_api_request_v1_data_options(value);
555         }
556         else if(!strcmp(name, "callback")) {
557             responseHandler = value;
558         }
559         else if(!strcmp(name, "filename")) {
560             outFileName = value;
561         }
562         else if(!strcmp(name, "tqx")) {
563             // parse Google Visualization API options
564             // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
565             char *tqx_name, *tqx_value;
566
567             while(value) {
568                 tqx_value = mystrsep(&value, ";");
569                 if(!tqx_value || !*tqx_value) continue;
570
571                 tqx_name = mystrsep(&tqx_value, ":");
572                 if(!tqx_name || !*tqx_name) continue;
573                 if(!tqx_value || !*tqx_value) continue;
574
575                 if(!strcmp(tqx_name, "version"))
576                     google_version = tqx_value;
577                 else if(!strcmp(tqx_name, "reqId"))
578                     google_reqId = tqx_value;
579                 else if(!strcmp(tqx_name, "sig")) {
580                     google_sig = tqx_value;
581                     google_timestamp = strtoul(google_sig, NULL, 0);
582                 }
583                 else if(!strcmp(tqx_name, "out")) {
584                     google_out = tqx_value;
585                     format = web_client_api_request_v1_data_google_format(google_out);
586                 }
587                 else if(!strcmp(tqx_name, "responseHandler"))
588                     responseHandler = tqx_value;
589                 else if(!strcmp(tqx_name, "outFileName"))
590                     outFileName = tqx_value;
591             }
592         }
593     }
594
595     if(!chart || !*chart) {
596         buffer_sprintf(w->response.data, "No chart id is given at the request.");
597         goto cleanup;
598     }
599
600     RRDSET *st = rrdset_find(host, chart);
601     if(!st) st = rrdset_find_byname(host, chart);
602     if(!st) {
603         buffer_strcat(w->response.data, "Chart is not found: ");
604         buffer_strcat_htmlescape(w->response.data, chart);
605         ret = 404;
606         goto cleanup;
607     }
608     st->last_accessed_time = now_realtime_sec();
609
610     long long before = (before_str && *before_str)?str2l(before_str):0;
611     long long after  = (after_str  && *after_str) ?str2l(after_str):0;
612     int       points = (points_str && *points_str)?str2i(points_str):0;
613
614     debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', format '%u', options '0x%08x'"
615           , w->id
616           , chart
617           , (dimensions)?buffer_tostring(dimensions):""
618           , after
619           , before
620           , points
621           , group
622           , format
623           , options
624     );
625
626     if(outFileName && *outFileName) {
627         buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
628         debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
629     }
630
631     if(format == DATASOURCE_DATATABLE_JSONP) {
632         if(responseHandler == NULL)
633             responseHandler = "google.visualization.Query.setResponse";
634
635         debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
636                 w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
637         );
638
639         buffer_sprintf(w->response.data,
640                 "%s({version:'%s',reqId:'%s',status:'ok',sig:'%ld',table:",
641                 responseHandler, google_version, google_reqId, st->last_updated.tv_sec);
642     }
643     else if(format == DATASOURCE_JSONP) {
644         if(responseHandler == NULL)
645             responseHandler = "callback";
646
647         buffer_strcat(w->response.data, responseHandler);
648         buffer_strcat(w->response.data, "(");
649     }
650
651     ret = rrdset2anything_api_v1(st, w->response.data, dimensions, format, points, after, before, group, options
652                                  , &last_timestamp_in_data);
653
654     if(format == DATASOURCE_DATATABLE_JSONP) {
655         if(google_timestamp < last_timestamp_in_data)
656             buffer_strcat(w->response.data, "});");
657
658         else {
659             // the client already has the latest data
660             buffer_flush(w->response.data);
661             buffer_sprintf(w->response.data,
662                     "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
663                     responseHandler, google_version, google_reqId);
664         }
665     }
666     else if(format == DATASOURCE_JSONP)
667         buffer_strcat(w->response.data, ");");
668
669     cleanup:
670     buffer_free(dimensions);
671     return ret;
672 }
673
674 inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url) {
675     static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
676             hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
677             hash_to = 0 /*, hash_redirects = 0 */;
678
679     if(unlikely(!hash_action)) {
680         hash_action = simple_hash("action");
681         hash_access = simple_hash("access");
682         hash_hello = simple_hash("hello");
683         hash_delete = simple_hash("delete");
684         hash_search = simple_hash("search");
685         hash_switch = simple_hash("switch");
686         hash_machine = simple_hash("machine");
687         hash_url = simple_hash("url");
688         hash_name = simple_hash("name");
689         hash_delete_url = simple_hash("delete_url");
690         hash_for = simple_hash("for");
691         hash_to = simple_hash("to");
692 /*
693         hash_redirects = simple_hash("redirects");
694 */
695     }
696
697     char person_guid[GUID_LEN + 1] = "";
698
699     debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
700
701     // FIXME
702     // The browser may send multiple cookies with our id
703
704     char *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "=");
705     if(cookie)
706         strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], 36);
707
708     char action = '\0';
709     char *machine_guid = NULL,
710             *machine_url = NULL,
711             *url_name = NULL,
712             *search_machine_guid = NULL,
713             *delete_url = NULL,
714             *to_person_guid = NULL;
715 /*
716     int redirects = 0;
717 */
718
719     while(url) {
720         char *value = mystrsep(&url, "?&");
721         if (!value || !*value) continue;
722
723         char *name = mystrsep(&value, "=");
724         if (!name || !*name) continue;
725         if (!value || !*value) continue;
726
727         debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
728
729         uint32_t hash = simple_hash(name);
730
731         if(hash == hash_action && !strcmp(name, "action")) {
732             uint32_t vhash = simple_hash(value);
733
734             if(vhash == hash_access && !strcmp(value, "access")) action = 'A';
735             else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H';
736             else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D';
737             else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
738             else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
739 #ifdef NETDATA_INTERNAL_CHECKS
740             else error("unknown registry action '%s'", value);
741 #endif /* NETDATA_INTERNAL_CHECKS */
742         }
743 /*
744         else if(hash == hash_redirects && !strcmp(name, "redirects"))
745             redirects = atoi(value);
746 */
747         else if(hash == hash_machine && !strcmp(name, "machine"))
748             machine_guid = value;
749
750         else if(hash == hash_url && !strcmp(name, "url"))
751             machine_url = value;
752
753         else if(action == 'A') {
754             if(hash == hash_name && !strcmp(name, "name"))
755                 url_name = value;
756         }
757         else if(action == 'D') {
758             if(hash == hash_delete_url && !strcmp(name, "delete_url"))
759                 delete_url = value;
760         }
761         else if(action == 'S') {
762             if(hash == hash_for && !strcmp(name, "for"))
763                 search_machine_guid = value;
764         }
765         else if(action == 'W') {
766             if(hash == hash_to && !strcmp(name, "to"))
767                 to_person_guid = value;
768         }
769 #ifdef NETDATA_INTERNAL_CHECKS
770         else error("unused registry URL parameter '%s' with value '%s'", name, value);
771 #endif /* NETDATA_INTERNAL_CHECKS */
772     }
773
774     if(respect_web_browser_do_not_track_policy && w->donottrack) {
775         buffer_flush(w->response.data);
776         buffer_sprintf(w->response.data, "Your web browser is sending 'DNT: 1' (Do Not Track). The registry requires persistent cookies on your browser to work.");
777         return 400;
778     }
779
780     if(action == 'A' && (!machine_guid || !machine_url || !url_name)) {
781         error("Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')",
782                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", url_name?url_name:"UNSET");
783         buffer_flush(w->response.data);
784         buffer_strcat(w->response.data, "Invalid registry Access request.");
785         return 400;
786     }
787     else if(action == 'D' && (!machine_guid || !machine_url || !delete_url)) {
788         error("Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')",
789                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
790         buffer_flush(w->response.data);
791         buffer_strcat(w->response.data, "Invalid registry Delete request.");
792         return 400;
793     }
794     else if(action == 'S' && (!machine_guid || !machine_url || !search_machine_guid)) {
795         error("Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')",
796                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", search_machine_guid?search_machine_guid:"UNSET");
797         buffer_flush(w->response.data);
798         buffer_strcat(w->response.data, "Invalid registry Search request.");
799         return 400;
800     }
801     else if(action == 'W' && (!machine_guid || !machine_url || !to_person_guid)) {
802         error("Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')",
803                 machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
804         buffer_flush(w->response.data);
805         buffer_strcat(w->response.data, "Invalid registry Switch request.");
806         return 400;
807     }
808
809     switch(action) {
810         case 'A':
811             w->tracking_required = 1;
812             return registry_request_access_json(host, w, person_guid, machine_guid, machine_url, url_name, now_realtime_sec());
813
814         case 'D':
815             w->tracking_required = 1;
816             return registry_request_delete_json(host, w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec());
817
818         case 'S':
819             w->tracking_required = 1;
820             return registry_request_search_json(host, w, person_guid, machine_guid, machine_url, search_machine_guid, now_realtime_sec());
821
822         case 'W':
823             w->tracking_required = 1;
824             return registry_request_switch_json(host, w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec());
825
826         case 'H':
827             return registry_request_hello_json(host, w);
828
829         default:
830             buffer_flush(w->response.data);
831             buffer_strcat(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
832             return 400;
833     }
834 }
835
836 inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) {
837     static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0, hash_badge = 0, hash_alarms = 0, hash_alarm_log = 0, hash_alarm_variables = 0, hash_raw = 0;
838
839     if(unlikely(hash_data == 0)) {
840         hash_data = simple_hash("data");
841         hash_chart = simple_hash("chart");
842         hash_charts = simple_hash("charts");
843         hash_registry = simple_hash("registry");
844         hash_badge = simple_hash("badge.svg");
845         hash_alarms = simple_hash("alarms");
846         hash_alarm_log = simple_hash("alarm_log");
847         hash_alarm_variables = simple_hash("alarm_variables");
848         hash_raw = simple_hash("allmetrics");
849     }
850
851     // get the command
852     char *tok = mystrsep(&url, "/?&");
853     if(tok && *tok) {
854         debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok);
855         uint32_t hash = simple_hash(tok);
856
857         if(hash == hash_data && !strcmp(tok, "data"))
858             return web_client_api_request_v1_data(host, w, url);
859
860         else if(hash == hash_chart && !strcmp(tok, "chart"))
861             return web_client_api_request_v1_chart(host, w, url);
862
863         else if(hash == hash_charts && !strcmp(tok, "charts"))
864             return web_client_api_request_v1_charts(host, w, url);
865
866         else if(hash == hash_registry && !strcmp(tok, "registry"))
867             return web_client_api_request_v1_registry(host, w, url);
868
869         else if(hash == hash_badge && !strcmp(tok, "badge.svg"))
870             return web_client_api_request_v1_badge(host, w, url);
871
872         else if(hash == hash_alarms && !strcmp(tok, "alarms"))
873             return web_client_api_request_v1_alarms(host, w, url);
874
875         else if(hash == hash_alarm_log && !strcmp(tok, "alarm_log"))
876             return web_client_api_request_v1_alarm_log(host, w, url);
877
878         else if(hash == hash_alarm_variables && !strcmp(tok, "alarm_variables"))
879             return web_client_api_request_v1_alarm_variables(host, w, url);
880
881         else if(hash == hash_raw && !strcmp(tok, "allmetrics"))
882             return web_client_api_request_v1_allmetrics(host, w, url);
883
884         else {
885             buffer_flush(w->response.data);
886             buffer_strcat(w->response.data, "Unsupported v1 API command: ");
887             buffer_strcat_htmlescape(w->response.data, tok);
888             return 404;
889         }
890     }
891     else {
892         buffer_flush(w->response.data);
893         buffer_sprintf(w->response.data, "Which API v1 command?");
894         return 400;
895     }
896 }