@cryptotaxi247 / netdata-1 / commits / 537e212e1

avl is now threat safe (found a condition where the trees could cause netdata to crash); added a still incomplete version of the new API

Costa Tsaousis (ktsaou) committed Nov 28, 2015 at 00:03 UTC 537e212e1d9e9d64c250d43f39791e9ff30159af
10 files changed +372 -22
src/appconfig.c
+3 -2
@@ -87,7 +87,8 @@ static int config_compare(void* a, void* b) {
87
88 avl_tree config_root_index = {
89 NULL,
90 - config_compare
90 + config_compare,
91 + PTHREAD_RWLOCK_INITIALIZER
92 };
93
94 #define config_index_add(cfg) avl_insert(&config_root_index, (avl *)(cfg))
@@ -141,7 +142,7 @@ struct config *config_create(const char *section)
142 if(!co->name) fatal("Cannot allocate config.name");
143 co->hash = simple_hash(co->name);
144
144 - co->values_index.compar = config_value_compare;
145 + avl_init(&co->values_index, config_value_compare);
146
147 config_index_add(co);
148
src/apps_plugin.c
+2 -1
@@ -830,7 +830,8 @@ int file_descriptor_iterator(avl *a) { if(a) {}; return 0; }
830
831 avl_tree all_files_index = {
832 NULL,
833 - file_descriptor_compare
833 + file_descriptor_compare,
834 + PTHREAD_RWLOCK_INITIALIZER
835 };
836
837 static struct file_descriptor *file_descriptor_find(const char *name, uint32_t hash) {
src/avl.c
+45 -12
@@ -67,7 +67,7 @@ void avl_nasty(avl* root) {
67 * returns 1 if the depth of the tree has grown
68 * Warning: do not insert elements already present
69 */
70 -int avl_insert(avl_tree* t, avl* a) {
70 +int _avl_insert(avl_tree* t, avl* a) {
71 /* initialize */
72 a->left = 0;
73 a->right = 0;
@@ -84,7 +84,7 @@ int avl_insert(avl_tree* t, avl* a) {
84 avl_tree left_subtree;
85 left_subtree.root = t->root->left;
86 left_subtree.compar = t->compar;
87 - if (avl_insert(&left_subtree, a)) {
87 + if (_avl_insert(&left_subtree, a)) {
88 switch (t->root->balance--) {
89 case 1:
90 return 0;
@@ -115,7 +115,7 @@ int avl_insert(avl_tree* t, avl* a) {
115 avl_tree right_subtree;
116 right_subtree.root = t->root->right;
117 right_subtree.compar = t->compar;
118 - if (avl_insert(&right_subtree, a)) {
118 + if (_avl_insert(&right_subtree, a)) {
119 switch (t->root->balance++) {
120 case -1:
121 return 0;
@@ -142,16 +142,22 @@ int avl_insert(avl_tree* t, avl* a) {
142 }
143 }
144 }
145 +int avl_insert(avl_tree* t, avl* a) {
146 + pthread_rwlock_wrlock(&t->rwlock);
147 + int ret = _avl_insert(t, a);
148 + pthread_rwlock_unlock(&t->rwlock);
149 + return ret;
150 +}
151
152 /* Remove an element a from the AVL tree t
153 * returns -1 if the depth of the tree has shrunk
154 * Warning: if the element is not present in the tree,
155 * returns 0 as if it had been removed succesfully.
156 */
151 -int avl_remove(avl_tree* t, avl* a) {
157 +int _avl_remove(avl_tree* t, avl* a) {
158 int b;
159 if (t->root == a)
154 - return avl_removeroot(t);
160 + return _avl_removeroot(t);
161 b = t->compar(t->root, a);
162 if (b >= 0) {
163 /* remove from the left subtree */
@@ -159,7 +165,7 @@ int avl_remove(avl_tree* t, avl* a) {
165 avl_tree left_subtree;
166 if ((left_subtree.root = t->root->left)) {
167 left_subtree.compar = t->compar;
162 - ch = avl_remove(&left_subtree, a);
168 + ch = _avl_remove(&left_subtree, a);
169 t->root->left = left_subtree.root;
170 if (ch) {
171 switch (t->root->balance++) {
@@ -193,7 +199,7 @@ int avl_remove(avl_tree* t, avl* a) {
199 avl_tree right_subtree;
200 if ((right_subtree.root = t->root->right)) {
201 right_subtree.compar = t->compar;
196 - ch = avl_remove(&right_subtree, a);
202 + ch = _avl_remove(&right_subtree, a);
203 t->root->right = right_subtree.root;
204 if (ch) {
205 switch (t->root->balance--) {
@@ -224,10 +230,17 @@ int avl_remove(avl_tree* t, avl* a) {
230 return 0;
231 }
232
233 +int avl_remove(avl_tree* t, avl* a) {
234 + pthread_rwlock_wrlock(&t->rwlock);
235 + int ret = _avl_remove(t, a);
236 + pthread_rwlock_unlock(&t->rwlock);
237 + return ret;
238 +}
239 +
240 /* Remove the root of the AVL tree t
241 * Warning: dumps core if t is empty
242 */
230 -int avl_removeroot(avl_tree* t) {
243 +int _avl_removeroot(avl_tree* t) {
244 int ch;
245 avl* a;
246 if (!t->root->left) {
@@ -253,7 +266,7 @@ int avl_removeroot(avl_tree* t) {
266 while (a->left)
267 a = a->left;
268 }
256 - ch = avl_remove(t, a);
269 + ch = _avl_remove(t, a);
270 a->left = t->root->left;
271 a->right = t->root->right;
272 a->balance = t->root->balance;
@@ -263,12 +276,19 @@ int avl_removeroot(avl_tree* t) {
276 return 0;
277 }
278
279 +int avl_removeroot(avl_tree* t) {
280 + pthread_rwlock_wrlock(&t->rwlock);
281 + int ret = _avl_removeroot(t);
282 + pthread_rwlock_unlock(&t->rwlock);
283 + return ret;
284 +}
285 +
286 /* Iterate through elements in t from a range between a and b (inclusive)
287 * for each element calls iter(a) until it returns 0
288 * returns the last value returned by iterator or 0 if there were no calls
289 * Warning: a<=b must hold
290 */
271 -int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl* a), avl** ret) {
291 +int _avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl* a), avl** ret) {
292 int x, c = 0;
293 if (!t->root)
294 return 0;
@@ -285,7 +305,7 @@ int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl* a), avl** ret) {
305 avl_tree left_subtree;
306 if ((left_subtree.root = t->root->left)) {
307 left_subtree.compar = t->compar;
288 - if (!(c = avl_range(&left_subtree, a, b, iter, ret)))
308 + if (!(c = _avl_range(&left_subtree, a, b, iter, ret)))
309 if (x > 0)
310 return 0;
311 }
@@ -302,7 +322,7 @@ int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl* a), avl** ret) {
322 avl_tree right_subtree;
323 if ((right_subtree.root = t->root->right)) {
324 right_subtree.compar = t->compar;
305 - if (!(c = avl_range(&right_subtree, a, b, iter, ret)))
325 + if (!(c = _avl_range(&right_subtree, a, b, iter, ret)))
326 if (x < 0)
327 return 0;
328 }
@@ -310,6 +330,13 @@ int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl* a), avl** ret) {
330 return c;
331 }
332
333 +int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl* a), avl** ret) {
334 + pthread_rwlock_rdlock(&t->rwlock);
335 + int ret2 = _avl_range(t, a, b, iter, ret);
336 + pthread_rwlock_unlock(&t->rwlock);
337 + return ret2;
338 +}
339 +
340 /* Iterate through elements in t equal to a
341 * for each element calls iter(a) until it returns 0
342 * returns the last value returned by iterator or 0 if there were no calls
@@ -317,3 +344,9 @@ int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl* a), avl** ret) {
344 int avl_search(avl_tree* t, avl* a, int (*iter)(avl* a), avl** ret) {
345 return avl_range(t, a, a, iter, ret);
346 }
347 +
348 +void avl_init(avl_tree* t, int (*compar)(void* a, void* b)) {
349 + t->root = NULL;
350 + t->compar = compar;
351 + pthread_rwlock_init(&t->rwlock, NULL);
352 +}
src/avl.h
+6
@@ -12,6 +12,7 @@
12 * Released under GNU General Public License (GPL) version 2
13 *
14 */
15 +#include <pthread.h>
16
17 #ifndef _AVL_H
18 #define _AVL_H 1
@@ -29,6 +30,7 @@ typedef struct avl {
30 typedef struct avl_tree {
31 avl* root;
32 int (*compar)(void* a, void* b);
33 + pthread_rwlock_t rwlock;
34 } avl_tree;
35
36 /* Public methods */
@@ -64,4 +66,8 @@ int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl* a), avl** ret);
66 */
67 int avl_search(avl_tree* t, avl* a, int (*iter)(avl* a), avl** ret);
68
69 +/* Initialize the avl_tree
70 + */
71 +void avl_init(avl_tree* t, int (*compar)(void* a, void* b));
72 +
73 #endif /* avl.h */
src/dictionary.c
+1 -1
@@ -88,7 +88,7 @@ DICTIONARY *dictionary_create(void) {
88 DICTIONARY *dict = calloc(1, sizeof(DICTIONARY));
89 if(!dict) fatal("Cannot allocate DICTIONARY");
90
91 - dict->values_index.compar = name_value_compare;
91 + avl_init(&dict->values_index, name_value_compare);
92 pthread_rwlock_init(&dict->rwlock, NULL);
93
94 return dict;
src/plugin_tc.c
+3 -1
@@ -77,7 +77,8 @@ static int tc_device_compare(void* a, void* b) {
77
78 avl_tree tc_device_root_index = {
79 NULL,
80 - tc_device_compare
80 + tc_device_compare,
81 + PTHREAD_RWLOCK_INITIALIZER
82 };
83
84 #define tc_device_index_add(st) avl_insert(&tc_device_root_index, (avl *)(st))
@@ -323,6 +324,7 @@ static struct tc_device *tc_device_create(char *id)
324
325 d->classes_index.root = NULL;
326 d->classes_index.compar = tc_class_compare;
327 + pthread_rwlock_init(&d->classes_index.rwlock, NULL);
328
329 tc_device_index_add(d);
330 }
src/rrd.c
+14 -4
@@ -26,6 +26,8 @@
26 // globals
27
28 // if not zero it gives the time (in seconds) to remove un-updated dimensions
29 +// DO NOT ENABLE
30 +// if dimensions are removed, the chart generation will have to run again
31 int rrd_delete_unupdated_dimensions = 0;
32
33 int rrd_update_every = UPDATE_EVERY;
@@ -50,7 +52,8 @@ static int rrdset_compare(void* a, void* b) {
52
53 avl_tree rrdset_root_index = {
54 NULL,
53 - rrdset_compare
55 + rrdset_compare,
56 + PTHREAD_RWLOCK_INITIALIZER
57 };
58
59 #define rrdset_index_add(st) avl_insert(&rrdset_root_index, (avl *)(st))
@@ -86,7 +89,8 @@ static int rrdset_compare_name(void* a, void* b) {
89
90 avl_tree rrdset_root_index_name = {
91 NULL,
89 - rrdset_compare_name
92 + rrdset_compare_name,
93 + PTHREAD_RWLOCK_INITIALIZER
94 };
95
96 int rrdset_index_add_name(RRDSET *st) {
@@ -318,6 +322,7 @@ void rrdset_reset(RRDSET *st)
322 for(rd = st->dimensions; rd ; rd = rd->next) {
323 rd->last_collected_time.tv_sec = 0;
324 rd->last_collected_time.tv_usec = 0;
325 + rd->counter = 0;
326 bzero(rd->values, rd->entries * sizeof(storage_number));
327 }
328 }
@@ -426,8 +431,7 @@ RRDSET *rrdset_create(const char *type, const char *id, const char *name, const
431
432 st->gap_when_lost_iterations_above = config_get_number(st->id, "gap when lost iterations above", RRD_DEFAULT_GAP_INTERPOLATIONS) + 2;
433
429 - st->dimensions_index.root = NULL;
430 - st->dimensions_index.compar = rrddim_compare;
434 + avl_init(&st->dimensions_index, rrddim_compare);
435
436 pthread_rwlock_init(&st->rwlock, NULL);
437 pthread_rwlock_wrlock(&rrdset_root_rwlock);
@@ -554,6 +558,9 @@ RRDDIM *rrddim_add(RRDSET *st, const char *id, const char *name, long multiplier
558 rd->entries = st->entries;
559 rd->update_every = st->update_every;
560
561 + // prevent incremental calculation spikes
562 + rd->counter = 0;
563 +
564 // append this dimension
565 if(!st->dimensions)
566 st->dimensions = rd;
@@ -813,6 +820,9 @@ unsigned long long rrdset_done(RRDSET *st)
820 // a read lock is OK here
821 pthread_rwlock_rdlock(&st->rwlock);
822
823 + // enable the chart, if it was disabled
824 + st->enabled = 1;
825 +
826 // check if the chart has a long time to be refreshed
827 if(unlikely(st->usec_since_last_update > st->entries * st->update_every * 1000000ULL)) {
828 info("%s: took too long to be updated (%0.3Lf secs). Reseting it.", st->name, (long double)(st->usec_since_last_update / 1000000.0));
src/rrd2json.c
+99
@@ -12,6 +12,105 @@
12 #define HOSTNAME_MAX 1024
13 char *hostname = "unknown";
14
15 +void rrd_stats_api_v1_chart(RRDSET *st, struct web_buffer *wb)
16 +{
17 + time_t now = time(NULL);
18 +
19 + pthread_rwlock_rdlock(&st->rwlock);
20 +
21 + web_buffer_snprintf(wb, 65536,
22 + "\t\t{\n"
23 + "\t\t\t\"id\": \"%s\",\n"
24 + "\t\t\t\"name\": \"%s\",\n"
25 + "\t\t\t\"type\": \"%s\",\n"
26 + "\t\t\t\"family\": \"%s\",\n"
27 + "\t\t\t\"title\": \"%s\",\n"
28 + "\t\t\t\"priority\": %ld,\n"
29 + "\t\t\t\"enabled\": %s,\n"
30 + "\t\t\t\"units\": \"%s\",\n"
31 + "\t\t\t\"data_url\": \"/api/v1/data?chart=%s\",\n"
32 + "\t\t\t\"chart_type\": \"%s\",\n"
33 + "\t\t\t\"duration\": %ld,\n"
34 + "\t\t\t\"first_entry\": %lu,\n"
35 + "\t\t\t\"last_entry\": %lu,\n"
36 + "\t\t\t\"update_every\": %d,\n"
37 + "\t\t\t\"dimensions\": {\n"
38 + , st->id
39 + , st->name
40 + , st->type
41 + , st->family
42 + , st->title
43 + , st->priority
44 + , st->enabled?"true":"false"
45 + , st->units
46 + , st->name
47 + , rrdset_type_name(st->chart_type)
48 + , st->entries * st->update_every
49 + , rrdset_first_entry_t(st)
50 + , rrdset_last_entry_t(st)
51 + , st->update_every
52 + );
53 +
54 + unsigned long memory = st->memsize;
55 +
56 + int c = 0;
57 + RRDDIM *rd;
58 + for(rd = st->dimensions; rd ; rd = rd->next) {
59 + if(rd->flags & RRDDIM_FLAG_HIDDEN) continue;
60 +
61 + memory += rd->memsize;
62 +
63 + web_buffer_snprintf(wb, 4096,
64 + "%s"
65 + "\t\t\t\t\"%s\": { \"name\": \"%s\" }"
66 + , c?",\n":""
67 + , rd->id
68 + , rd->name
69 + );
70 +
71 + c++;
72 + }
73 +
74 + web_buffer_snprintf(wb, 200,
75 + "\n\t\t\t}\n"
76 + "\t\t}"
77 + );
78 +
79 + pthread_rwlock_unlock(&st->rwlock);
80 +}
81 +
82 +void rrd_stats_api_v1_charts(struct web_buffer *wb)
83 +{
84 + long c;
85 + RRDSET *st;
86 +
87 + web_buffer_snprintf(wb, 4096, "{\n"
88 + "\t\"hostname\": \"%s\""
89 + ",\n\t\"update_every\": %d"
90 + ",\n\t\"history\": %d"
91 + ",\n\t\"charts\": {"
92 + , hostname
93 + , rrd_update_every
94 + , rrd_default_history_entries
95 + );
96 +
97 + pthread_rwlock_rdlock(&rrdset_root_rwlock);
98 + for(st = rrdset_root, c = 0; st ; st = st->next) {
99 + if(st->enabled) {
100 + if(c) web_buffer_strcat(wb, ",");
101 + web_buffer_strcat(wb, "\n\t\t\"");
102 + web_buffer_strcat(wb, st->id);
103 + web_buffer_strcat(wb, "\": ");
104 + rrd_stats_api_v1_chart(st, wb);
105 + c++;
106 + }
107 + }
108 + pthread_rwlock_unlock(&rrdset_root_rwlock);
109 +
110 + web_buffer_strcat(wb, "\n\t}\n}\n");
111 +}
112 +
113 +
114 unsigned long rrd_stats_one_json(RRDSET *st, char *options, struct web_buffer *wb)
115 {
116 time_t now = time(NULL);
src/rrd2json.h
+6
@@ -10,14 +10,20 @@
10 extern char *hostname;
11
12 // type of JSON generations
13 +#define DATASOURCE_INVALID -1
14 #define DATASOURCE_JSON 0
15 #define DATASOURCE_GOOGLE_JSON 1
16 #define DATASOURCE_GOOGLE_JSONP 2
17 +#define DATASOURCE_SSV 3
18 +#define DATASOURCE_CSV 4
19
20 #define GROUP_AVERAGE 0
21 #define GROUP_MAX 1
22 #define GROUP_SUM 2
23
24 +extern void rrd_stats_api_v1_chart(RRDSET *st, struct web_buffer *wb);
25 +extern void rrd_stats_api_v1_charts(struct web_buffer *wb);
26 +
27 extern unsigned long rrd_stats_one_json(RRDSET *st, char *options, struct web_buffer *wb);
28
29 extern void rrd_stats_graph_json(RRDSET *st, char *options, struct web_buffer *wb);
src/web_client.c
+193 -1
@@ -327,6 +327,193 @@ void web_client_enable_deflate(struct web_client *w) {
327 }
328 #endif // NETDATA_WITH_ZLIB
329
330 +int rrdset_validate_dimensions(RRDSET *st, struct web_buffer *dimensions)
331 +{
332 + ;
333 +}
334 +
335 +int web_client_api_request_v1_data_format(char *name)
336 +{
337 + if(!strcmp(name, "datatable"))
338 + return DATASOURCE_GOOGLE_JSON;
339 +
340 + else if(!strcmp(name, "datasource"))
341 + return DATASOURCE_GOOGLE_JSONP;
342 +
343 + else if(!strcmp(name, "json"))
344 + return DATASOURCE_JSON;
345 +
346 + else if(!strcmp(name, "ssv"))
347 + return DATASOURCE_SSV;
348 +
349 + else if(!strcmp(name, "csv"))
350 + return DATASOURCE_CSV;
351 +
352 + return DATASOURCE_INVALID;
353 +}
354 +
355 +int web_client_api_request_v1_charts(struct web_client *w, char *url)
356 +{
357 + web_buffer_flush(w->data);
358 + rrd_stats_api_v1_charts(w->data);
359 + return 200;
360 +}
361 +
362 +int web_client_api_request_v1_chart(struct web_client *w, char *url)
363 +{
364 + int ret = 400;
365 + char *chart = NULL;
366 +
367 + web_buffer_flush(w->data);
368 +
369 + while(url) {
370 + char *value = mystrsep(&url, "?&[]");
371 + if(!value || !*value) continue;
372 +
373 + char *name = mystrsep(&value, "=");
374 + if(!name || !*name) continue;
375 + if(!value || !*value) continue;
376 +
377 + // name and value are now the parameters
378 + // they are not null and not empty
379 +
380 + if(!strcmp(name, "chart")) chart = value;
381 + else {
382 + web_buffer_snprintf(w->data, 1024, "Unknown parameter '%s' in request.", name);
383 + goto cleanup;
384 + }
385 + }
386 +
387 + if(!chart || !*chart) {
388 + web_buffer_snprintf(w->data, 1024, "No chart id is given at the request.");
389 + goto cleanup;
390 + }
391 +
392 + RRDSET *st = rrdset_find(chart);
393 + if(!st) st = rrdset_find_byname(chart);
394 + if(!st) {
395 + web_buffer_snprintf(w->data, 1024, "Chart '%s' is not found.", chart);
396 + ret = 404;
397 + goto cleanup;
398 + }
399 +
400 + w->data->contenttype = CT_APPLICATION_JSON;
401 + rrd_stats_api_v1_chart(st, w->data);
402 + return 200;
403 +
404 +cleanup:
405 + return ret;
406 +}
407 +
408 +int web_client_api_request_v1_data(struct web_client *w, char *url)
409 +{
410 + int ret = 400;
411 + struct web_buffer *dimensions = NULL;
412 +
413 + web_buffer_flush(w->data);
414 +
415 + char *chart = NULL
416 + , *dim = NULL
417 + , *before_str = NULL
418 + , *after_str = NULL
419 + , *points_str = NULL
420 + , *group_str = NULL
421 + , *options = NULL;
422 +
423 + int format = DATASOURCE_JSON;
424 +
425 + while(url) {
426 + char *value = mystrsep(&url, "?&[]");
427 + if(!value || !*value) continue;
428 +
429 + char *name = mystrsep(&value, "=");
430 + if(!name || !*name) continue;
431 + if(!value || !*value) continue;
432 +
433 + // name and value are now the parameters
434 + // they are not null and not empty
435 +
436 + if(!strcmp(name, "chart")) chart = value;
437 + else if(!strcmp(name, "dimension") || !strcmp(name, "dim")) {
438 + if(!dimensions) dimensions = web_buffer_create(strlen(value));
439 + if(dimensions) {
440 + web_buffer_strcat(dimensions, "|");
441 + web_buffer_strcat(dimensions, value);
442 + }
443 + }
444 + else if(!strcmp(name, "after")) after_str = value;
445 + else if(!strcmp(name, "before")) before_str = value;
446 + else if(!strcmp(name, "points")) points_str = value;
447 + else if(!strcmp(name, "group")) group_str = value;
448 + else if(!strcmp(name, "format")) {
449 + format = web_client_api_request_v1_data_format(value);
450 + }
451 + else if(!strcmp(name, "options")) {
452 + options = value;
453 + }
454 + else {
455 + web_buffer_snprintf(w->data, 1024, "Unknown parameter '%s' in request.", name);
456 + goto cleanup;
457 + }
458 + }
459 +
460 + if(!chart || !*chart) {
461 + web_buffer_snprintf(w->data, 1024, "No chart id is given at the request.");
462 + goto cleanup;
463 + }
464 +
465 + RRDSET *st = rrdset_find(chart);
466 + if(!st) st = rrdset_find_byname(chart);
467 + if(!st) {
468 + web_buffer_snprintf(w->data, 1024, "Chart '%s' is not found.", chart);
469 + ret = 404;
470 + goto cleanup;
471 + }
472 +
473 + uint32_t before = (before_str && *before_str)?atol(before_str):0;
474 + uint32_t after = (after_str && *after_str) ?atol(after_str):0;
475 + int points = (points_str && *points_str)?atoi(points_str):0;
476 + int group = (group_str && *group_str) ?atoi(group_str):0;
477 +
478 + web_buffer_snprintf(w->data, 1024, "API command 'data' for chart '%s', dimensions '%s', after '%s', before '%s', points '%s', group '%s', format '%s', options '%s'", chart, dim, after_str, before_str, points_str, group_str, format, options);
479 +
480 +cleanup:
481 + if(dimensions) web_buffer_free(dimensions);
482 + return ret;
483 +}
484 +
485 +int web_client_api_request_v1(struct web_client *w, char *url)
486 +{
487 + // get the command
488 + char *tok = mystrsep(&url, "/?&");
489 + debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok);
490 +
491 + if(strcmp(tok, "data") == 0)
492 + return web_client_api_request_v1_data(w, url);
493 + else if(strcmp(tok, "chart") == 0)
494 + return web_client_api_request_v1_chart(w, url);
495 + else if(strcmp(tok, "charts") == 0)
496 + return web_client_api_request_v1_charts(w, url);
497 +
498 + web_buffer_flush(w->data);
499 + web_buffer_snprintf(w->data, 1024, "Unsupported v1 API command: %s", tok);
500 + return 404;
501 +}
502 +
503 +int web_client_api_request(struct web_client *w, char *url)
504 +{
505 + // get the api version
506 + char *tok = mystrsep(&url, "/?&");
507 + debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok);
508 +
509 + if(strcmp(tok, "v1") == 0)
510 + return web_client_api_request_v1(w, url);
511 +
512 + web_buffer_flush(w->data);
513 + web_buffer_snprintf(w->data, 1024, "Unsupported API version: %s", tok);
514 + return 404;
515 +}
516 +
517 int web_client_data_request(struct web_client *w, char *url, int datasource_type)
518 {
519 char *args = strchr(url, '?');
@@ -587,7 +774,12 @@ void web_client_process(struct web_client *w) {
774
775 debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
776
590 - if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data"
777 + if(strcmp(tok, "api") == 0) {
778 + // the client is requesting api access
779 + datasource_type = DATASOURCE_JSON;
780 + code = web_client_api_request(w, url);
781 + }
782 + else if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data"
783 // the client is requesting rrd data
784 datasource_type = DATASOURCE_JSON;
785 code = web_client_data_request(w, url, datasource_type);