]> arthur.barton.de Git - netdata.git/commitdiff
first complete API implementation - it now supports multiple different visualizations
authorCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Sun, 29 Nov 2015 01:51:19 +0000 (03:51 +0200)
committerCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Sun, 29 Nov 2015 01:51:19 +0000 (03:51 +0200)
src/avl.c
src/rrd2json.c
src/rrd2json.h
src/web_buffer.c
src/web_buffer.h
src/web_client.c

index c41d5f6dd7ce37cc033b6af9e3105fc09593d1e0..9cc72283d84dad626a883127da3031a37d423bb1 100755 (executable)
--- a/src/avl.c
+++ b/src/avl.c
@@ -19,6 +19,7 @@
 #include "avl.h"
 
 /* Private methods */
+int _avl_removeroot(avl_tree* t);
 
 /* Swing to the left
  * Warning: no balance maintainance
index a12369ff451ea9eede665384b46873459c455e12..bdd0400aaa59aad485527b5cfc275cc3f5305cde 100755 (executable)
@@ -4,6 +4,7 @@
 #include <pthread.h>
 #include <sys/time.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include "log.h"
 #include "common.h"
@@ -14,8 +15,6 @@ char *hostname = "unknown";
 
 void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb)
 {
-       time_t now = time(NULL);
-
        pthread_rwlock_rdlock(&st->rwlock);
 
        buffer_sprintf(wb,
@@ -261,10 +260,10 @@ void rrd_stats_all_json(BUFFER *wb)
 // ----------------------------------------------------------------------------
 
 // RRDR options
-#define RRDR_EMPTY     0x01
-#define RRDR_RESET     0x02
-#define RRDR_HIDDEN    0x04
-#define RRDR_NONZERO   0x08
+#define RRDR_EMPTY     0x01 // the dimension contains / the value is empty (null)
+#define RRDR_RESET     0x02 // the dimension contains / the value is reset
+#define RRDR_HIDDEN    0x04 // the dimension contains / the value is hidden
+#define RRDR_NONZERO   0x08 // the dimension contains / the value is non-zero
 
 
 typedef struct rrdresult {
@@ -279,11 +278,14 @@ typedef struct rrdresult {
        calculated_number *v;   // array n x d values
        uint8_t *o;                             // array n x d options
 
-       int c;                                  // current line ( 0 ~ n )
+       int c;                                  // current line ( -1 ~ n ), ( -1 = none, use rrdr_rows() to get number of rows )
 
        int has_st_lock;                // if st is read locked by us
 } RRDR;
 
+#define rrdr_rows(r) ((r)->c + 1)
+
+/*
 static void rrdr_dump(RRDR *r)
 {
        long c, i;
@@ -337,6 +339,353 @@ static void rrdr_dump(RRDR *r)
                fprintf(stderr, "\n");
        }
 }
+*/
+
+void rrdr_disable_not_selected_dimensions(RRDR *r, const char *dims)
+{
+       char b[strlen(dims) + 1];
+       char *o = b, *tok;
+       strcpy(o, dims);
+
+       long c;
+       RRDDIM *d;
+
+       // disable all of them
+       for(c = 0, d = r->st->dimensions; d ;c++, d = d->next)
+               r->od[c] |= RRDR_HIDDEN;
+
+       while(o && *o && (tok = mystrsep(&o, ", |"))) {
+               if(!*tok) continue;
+
+               // find it and enable it
+               for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) {
+                       if(!strcmp(d->name, tok)) {
+                               r->od[c] &= ~RRDR_HIDDEN;
+                       }
+               }
+       }
+}
+
+#define JSON_DATES_JS 1
+#define JSON_DATES_TIMESTAMP 2
+
+static void rrdr2json(RRDR *r, BUFFER *wb, uint32_t options, int google)
+{
+       int annotations = 0, dates = JSON_DATES_JS;
+       char kq[2] = "",                                        // key quote
+               sq[2] = "",                                             // string quote
+               pre_label[101] = "",                    // before each label
+               post_label[101] = "",                   // after each label
+               pre_date[101] = "",                             // the beginning of line, to the date
+               post_date[101] = "",                    // closing the date
+               pre_value[101] = "",                    // before each value
+               post_value[101] = "",                   // after each value
+               post_line[101] = "",                    // at the end of each row
+               normal_annotation[201] = "",    // default row annotation
+               overflow_annotation[201] = "",  // overflow row annotation
+               data_begin[101] = "",                   // between labels and values
+               finish[101] = "";                               // at the end of everything
+
+       if(google) {
+               dates = JSON_DATES_JS;
+               kq[0] = '\0';
+               sq[0] = '\'';
+               annotations = 1;
+               snprintf(pre_date,   100, "             {%sc%s:[{%sv%s:%s", kq, kq, kq, kq, sq);
+               snprintf(post_date,  100, "%s}", sq);
+               snprintf(pre_label,  100, ",\n          {%sid%s:%s%s,%slabel%s:%s", kq, kq, sq, sq, kq, kq, sq);
+               snprintf(post_label, 100, "%s,%spattern%s:%s%s,%stype%s:%snumber%s}", sq, kq, kq, sq, sq, kq, kq, sq, sq);
+               snprintf(pre_value,  100, ",{%sv%s:", kq, kq);
+               snprintf(post_value, 100, "}");
+               snprintf(post_line,  100, "]}");
+               snprintf(data_begin, 100, "\n   ],\n    %srows%s:\n     [\n", kq, kq);
+               snprintf(finish,     100, "\n   ]\n}\n");
+
+               snprintf(overflow_annotation, 200, ",{%sv%s:%sRESET OR OVERFLOW%s},{%sv%s:%sThe counters have been wrapped.%s}", kq, kq, sq, sq, kq, kq, sq, sq);
+               snprintf(normal_annotation,   200, ",{%sv%s:null},{%sv%s:null}", kq, kq, kq, kq);
+
+               buffer_sprintf(wb, "{\n %scols%s:\n     [\n", kq, kq);
+               buffer_sprintf(wb, "            {%sid%s:%s%s,%slabel%s:%stime%s,%spattern%s:%s%s,%stype%s:%sdatetime%s},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq);
+               buffer_sprintf(wb, "            {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotation%s}},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq);
+               buffer_sprintf(wb, "            {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotationText%s}}", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq);
+       }
+       else {
+               if((options & RRDR_OPTION_SECONDS) || (options & RRDR_OPTION_MILLISECONDS)) {
+                       dates = JSON_DATES_TIMESTAMP;
+                       snprintf(pre_date,   100, "             [");
+               }
+               else {
+                       dates = JSON_DATES_JS;
+                       snprintf(pre_date,   100, "             [new ");
+               }
+               kq[0] = '"';
+               sq[0] = '"';
+               snprintf(pre_label,  100, ", \"");
+               snprintf(post_label, 100, "\"");
+               snprintf(pre_value,  100, ", ");
+               snprintf(post_line,  100, "]");
+               snprintf(data_begin, 100, "],\n %sdata%s:\n     [\n", kq, kq);
+               snprintf(finish,     100, "\n   ]\n}\n");
+
+               buffer_sprintf(wb, "{\n %slabels%s: [", kq, kq);
+               buffer_sprintf(wb, "%stime%s", sq, sq);
+       }
+
+       // -------------------------------------------------------------------------
+       // print the JSON header
+
+       long c, i;
+       RRDDIM *rd;
+
+       // print the csv header
+       for(c = 0, i = 0, rd = r->st->dimensions; rd ;c++, rd = rd->next) {
+               if(unlikely(r->od[c] & RRDR_HIDDEN)) continue;
+               if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue;
+
+               buffer_strcat(wb, pre_label);
+               buffer_strcat(wb, rd->name);
+               buffer_strcat(wb, post_label);
+               i++;
+       }
+       if(!i) {
+               buffer_strcat(wb, pre_label);
+               buffer_strcat(wb, "no data");
+               buffer_strcat(wb, post_label);
+       }
+
+       // print the begin of row data
+       buffer_strcat(wb, data_begin);
+
+       // if all dimensions are hidden, print a null
+       if(!i) {
+               buffer_strcat(wb, pre_value);
+               if(options & RRDR_OPTION_NULL2ZERO)
+                       buffer_strcat(wb, "0");
+               else
+                       buffer_strcat(wb, "null");
+               buffer_strcat(wb, post_value);
+       }
+
+       long start = 0, end = rrdr_rows(r), step = 1;
+       if((options & RRDR_OPTION_REVERSED)) {
+               start = rrdr_rows(r) - 1;
+               end = -1;
+               step = -1;
+       }
+
+       // for each line in the array
+       for(i = start; i != end ;i += step) {
+               calculated_number *cn = &r->v[ i * r->d ];
+               uint8_t *co = &r->o[ i * r->d ];
+
+               time_t now = r->t[i];
+
+               if(dates == JSON_DATES_JS) {
+                       // generate the local date time
+                       struct tm *tm = localtime(&now);
+                       if(!tm) { error("localtime() failed."); continue; }
+
+                       if(likely(i != start)) buffer_strcat(wb, ",\n");
+                       buffer_strcat(wb, pre_date);
+                       buffer_jsdate(wb, tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+                       buffer_strcat(wb, post_date);
+
+                       if(annotations) {
+                               if(co[c] & RRDR_RESET)
+                                       buffer_strcat(wb, overflow_annotation);
+                               else
+                                       buffer_strcat(wb, normal_annotation);
+                       }
+               }
+               else {
+                       // print the timestamp of the line
+                       if(likely(i != start)) buffer_strcat(wb, ",\n");
+                       buffer_strcat(wb, pre_date);
+                       buffer_rrd_value(wb, (calculated_number)r->t[i]);
+                       // in ms
+                       if(options & RRDR_OPTION_MILLISECONDS) buffer_strcat(wb, "000");
+                       buffer_strcat(wb, post_date);
+               }
+
+               // for each dimension
+               for(c = 0, rd = r->st->dimensions; rd ;c++, rd = rd->next) {
+                       if(unlikely(r->od[c] & RRDR_HIDDEN)) continue;
+                       if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue;
+
+                       calculated_number n = cn[c];
+
+                       if(co[c] & RRDR_EMPTY) {
+                               buffer_strcat(wb, pre_value);
+                               if(options & RRDR_OPTION_NULL2ZERO)
+                                       buffer_strcat(wb, "0");
+                               else
+                                       buffer_strcat(wb, "null");
+                               buffer_strcat(wb, post_value);
+                       }
+                       else if((options & RRDR_OPTION_ABSOLUTE)) {
+                               buffer_strcat(wb, pre_value);
+                               buffer_rrd_value(wb, (n<0)?-n:n);
+                               buffer_strcat(wb, post_value);
+                       }
+                       else {
+                               buffer_strcat(wb, pre_value);
+                               buffer_rrd_value(wb, n);
+                               buffer_strcat(wb, post_value);
+                       }
+               }
+
+               buffer_strcat(wb, post_line);
+       }
+
+       buffer_strcat(wb, finish);
+}
+
+static void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t options, const char *startline, const char *separator, const char *endline)
+{
+       long c, i;
+       RRDDIM *d;
+
+       // print the csv header
+       for(c = 0, i = 0, d = r->st->dimensions; d ;c++, d = d->next) {
+               if(unlikely(r->od[c] & RRDR_HIDDEN)) continue;
+               if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue;
+
+               if(!i) {
+                       buffer_strcat(wb, startline);
+                       buffer_strcat(wb, "time");
+               }
+               buffer_strcat(wb, separator);
+               buffer_strcat(wb, d->name);
+               i++;
+       }
+       buffer_strcat(wb, endline);
+
+       if(!i) {
+               // no dimensions present
+               return;
+       }
+
+       long start = 0, end = rrdr_rows(r), step = 1;
+       if((options & RRDR_OPTION_REVERSED)) {
+               start = rrdr_rows(r) - 1;
+               end = -1;
+               step = -1;
+       }
+
+       // for each line in the array
+       for(i = start; i != end ;i += step) {
+               calculated_number *cn = &r->v[ i * r->d ];
+               uint8_t *co = &r->o[ i * r->d ];
+
+               buffer_strcat(wb, startline);
+
+               time_t now = r->t[i];
+
+               if((options & RRDR_OPTION_SECONDS) || (options & RRDR_OPTION_MILLISECONDS)) {
+                       // print the timestamp of the line
+                       buffer_rrd_value(wb, (calculated_number)now);
+                       // in ms
+                       if(options & RRDR_OPTION_MILLISECONDS) buffer_strcat(wb, "000");
+               }
+               else {
+                       // generate the local date time
+                       struct tm *tm = localtime(&now);
+                       if(!tm) { error("localtime() failed."); continue; }
+                       buffer_date(wb, tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+               }
+
+               // for each dimension
+               for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) {
+                       if(unlikely(r->od[c] & RRDR_HIDDEN)) continue;
+                       if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue;
+
+                       buffer_strcat(wb, separator);
+
+                       calculated_number n = cn[c];
+
+                       if(co[c] & RRDR_EMPTY) {
+                               if(options & RRDR_OPTION_NULL2ZERO)
+                                       buffer_strcat(wb, "0");
+                               else
+                                       buffer_strcat(wb, "null");
+                       }
+                       else if((options & RRDR_OPTION_ABSOLUTE))
+                               buffer_rrd_value(wb, (n<0)?-n:n);
+                       else
+                               buffer_rrd_value(wb, n);
+               }
+
+               buffer_strcat(wb, endline);
+       }
+}
+
+static void rrdr2ssv(RRDR *r, BUFFER *out, uint32_t options, const char *prefix, const char *separator, const char *suffix)
+{
+       long c, i;
+       RRDDIM *d;
+
+       buffer_strcat(out, prefix);
+       long start = 0, end = rrdr_rows(r), step = 1;
+       if((options & RRDR_OPTION_REVERSED)) {
+               start = rrdr_rows(r) - 1;
+               end = -1;
+               step = -1;
+       }
+
+       // for each line in the array
+       for(i = start; i != end ;i += step) {
+
+               calculated_number *cn = &r->v[ i * r->d ];
+               uint8_t *co = &r->o[ i * r->d ];
+
+               calculated_number sum = 0, min = 0, max = 0;
+               int all_null = 1, init = 1;
+
+               // for each dimension
+               for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) {
+                       if(unlikely(r->od[c] & RRDR_HIDDEN)) continue;
+                       if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue;
+
+                       calculated_number n = cn[c];
+
+                       if(unlikely(init)) {
+                               if(n > 0) {
+                                       min = 0;
+                                       max = n;
+                               }
+                               else {
+                                       min = n;
+                                       max = 0;
+                               }
+                               init = 0;
+                       }
+
+                       if(likely(!(co[c] & RRDR_EMPTY))) {
+                               all_null = 0;
+                               if((options & RRDR_OPTION_ABSOLUTE) && n < 0) n = -n;
+                               sum += n;
+                       }
+
+                       if(n < min) min = n;
+                       if(n > max) max = n;
+               }
+
+               if(likely(i != start))
+                       buffer_strcat(out, separator);
+
+               if(all_null) {
+                       if(options & RRDR_OPTION_NULL2ZERO)
+                               buffer_strcat(out, "0");
+                       else
+                               buffer_strcat(out, "null");
+               }
+               else if(options & RRDR_OPTION_MIN2MAX)
+                       buffer_rrd_value(out, max - min);
+               else
+                       buffer_rrd_value(out, sum);
+       }
+       buffer_strcat(out, suffix);
+}
 
 inline static calculated_number *rrdr_line_values(RRDR *r)
 {
@@ -442,13 +791,18 @@ cleanup:
        return NULL;
 }
 
-RRDR *rrd2rrdr(RRDSET *st, long points, time_t after, time_t before, int group_method)
+RRDR *rrd2rrdr(RRDSET *st, long points, long long after, long long before, int group_method)
 {
        int debug = st->debug;
 
        time_t first_entry_t = rrdset_first_entry_t(st);
        time_t last_entry_t = rrdset_last_entry_t(st);
 
+       if(before == 0 && after == 0) {
+               before = last_entry_t;
+               after = first_entry_t;
+       }
+
        // allow relative for before and after
        if(before <= st->update_every * st->entries) before = last_entry_t + before;
        if(after <= st->update_every * st->entries) after = last_entry_t + after;
@@ -462,9 +816,9 @@ RRDR *rrd2rrdr(RRDSET *st, long points, time_t after, time_t before, int group_m
 
        // check if they are upside down
        if(after > before) {
-               time_t t = before;
+               time_t tmp = before;
                before = after;
-               after = t;
+               after = tmp;
        }
 
        // the duration of the chart
@@ -544,36 +898,49 @@ RRDR *rrd2rrdr(RRDSET *st, long points, time_t after, time_t before, int group_m
        // -------------------------------------------------------------------------
        // the main loop
 
-       long    t = rrdset_time2slot(st, before), // rrdset_last_slot(st),
-                       stop_at_t = rrdset_time2slot(st, after),
-                       added = 0,
-                       group_count = 0,
-                       add_this = 0,
-                       stop_now = 0;
+       long    start_at_slot = rrdset_time2slot(st, before), // rrdset_last_slot(st),
+                       stop_at_slot = rrdset_time2slot(st, after);
 
-       // align to group for proper panning of data
-       t -= t % group;
-
-       time_t  now = rrdset_slot2time(st, t),
+       time_t  now = rrdset_slot2time(st, start_at_slot),
                        dt = st->update_every,
                        group_start_t = 0;
 
-       if(debug) debug(D_RRD_STATS, "BEGIN %s after_t: %lu (stop slot %ld), before_t: %lu (start slot %ld), start_t(now): %lu"
+       if(unlikely(debug)) debug(D_RRD_STATS, "INFO  %s after_t: %lu (stop_at_t: %ld), before_t: %lu (start_at_t: %ld), start_t(now): %lu, current_entry: %ld, entries: %ld"
+                       , st->id
+                       , after
+                       , stop_at_slot
+                       , before
+                       , start_at_slot
+                       , now
+                       , st->current_entry
+                       , st->entries
+                       );
+
+       // align to group for proper panning of data
+       start_at_slot -= start_at_slot % group;
+       stop_at_slot -= stop_at_slot % group;
+       now = rrdset_slot2time(st, start_at_slot);
+
+       if(unlikely(debug)) debug(D_RRD_STATS, "BEGIN %s after_t: %lu (stop_at_t: %ld), before_t: %lu (start_at_t: %ld), start_t(now): %lu, current_entry: %ld, entries: %ld"
                        , st->id
                        , after
-                       , stop_at_t
+                       , stop_at_slot
                        , before
-                       , t
+                       , start_at_slot
                        , now
+                       , st->current_entry
+                       , st->entries
                        );
 
-       for( ; !stop_now ; now -= dt, t--) {
-               if(unlikely(t < 0)) c = st->entries - 1;
-               if(t == stop_at_t) stop_now = 1;
+       long slot = start_at_slot, counter = 0, stop_now = 0, added = 0, group_count = 0, add_this = 0;
+       for(; !stop_now ; now -= dt, slot--, counter++) {
+               if(unlikely(slot < 0)) slot = st->entries - 1;
+               if(unlikely(slot == stop_at_slot)) stop_now = counter;
 
-               if(debug) debug(D_RRD_STATS, "ROW %s c: %ld, group_count: %ld, added: %ld, now: %lu, %s %s"
+               if(unlikely(debug)) debug(D_RRD_STATS, "ROW %s slot: %ld, entries_counter: %ld, group_count: %ld, added: %ld, now: %lu, %s %s"
                                , st->id
-                               , t
+                               , slot
+                               , counter
                                , group_count + 1
                                , added
                                , now
@@ -585,7 +952,7 @@ RRDR *rrd2rrdr(RRDSET *st, long points, time_t after, time_t before, int group_m
                if(unlikely(now > before)) continue;
                if(unlikely(now < after)) break;
 
-               if(group_count == 0) group_start_t = now;
+               if(unlikely(group_count == 0)) group_start_t = now;
                group_count++;
 
                if(unlikely(group_count == group)) {
@@ -595,13 +962,13 @@ RRDR *rrd2rrdr(RRDSET *st, long points, time_t after, time_t before, int group_m
 
                // do the calculations
                for(rd = st->dimensions, c = 0 ; likely(rd && c < dimensions) ; rd = rd->next, c++) {
-                       storage_number n = rd->values[t];
+                       storage_number n = rd->values[slot];
                        if(unlikely(!does_storage_number_exist(n))) continue;
 
                        group_counts[c]++;
 
                        calculated_number value = unpack_storage_number(n);
-                       if(value != 0.0) {
+                       if(likely(value != 0.0)) {
                                group_options[c] |= RRDR_NONZERO;
                                found_non_zero[c] = 1;
                        }
@@ -625,7 +992,7 @@ RRDR *rrd2rrdr(RRDSET *st, long points, time_t after, time_t before, int group_m
 
                // added it
                if(unlikely(add_this)) {
-                       if(!rrdr_line_init(r, group_start_t)) break;
+                       if(unlikely(!rrdr_line_init(r, group_start_t))) break;
 
                        calculated_number *cn = rrdr_line_values(r);
                        uint8_t *co = rrdr_line_options(r);
@@ -633,14 +1000,14 @@ RRDR *rrd2rrdr(RRDSET *st, long points, time_t after, time_t before, int group_m
                        for(rd = st->dimensions, c = 0 ; likely(rd && c < dimensions) ; rd = rd->next, c++) {
 
                                // update the dimension options
-                               if(found_non_zero[c]) r->od[c] |= RRDR_NONZERO;
-                               if(rd->flags & RRDDIM_FLAG_HIDDEN) r->od[c] |= RRDR_HIDDEN;
+                               if(likely(found_non_zero[c])) r->od[c] |= RRDR_NONZERO;
+                               if(unlikely(rd->flags & RRDDIM_FLAG_HIDDEN)) r->od[c] |= RRDR_HIDDEN;
 
                                // store the specific point options
                                co[c] = group_options[c];
 
                                // store the value
-                               if(group_counts[c] == 0) {
+                               if(unlikely(group_counts[c] == 0)) {
                                        cn[c] = 0.0;
                                        co[c] |= RRDR_EMPTY;
                                }
@@ -663,9 +1030,80 @@ RRDR *rrd2rrdr(RRDSET *st, long points, time_t after, time_t before, int group_m
                }
        }
 
-       rrdr_dump(r);
-       rrdr_free(r);
-       return NULL;
+       return r;
+}
+
+int rrd2format(RRDSET *st, BUFFER *out, BUFFER *dimensions, uint32_t format, long points, long long after, long long before, int group_method, uint32_t options, time_t *latest_timestamp)
+{
+       RRDR *rrdr = rrd2rrdr(st, points, after, before, group_method);
+       if(!rrdr) {
+               buffer_strcat(out, "Cannot generate output with these parameters on this chart.");
+               return 500;
+       }
+
+       if(dimensions)
+               rrdr_disable_not_selected_dimensions(rrdr, buffer_tostring(dimensions));
+
+       if(latest_timestamp && rrdr_rows(rrdr) > 0)
+               *latest_timestamp = rrdr->t[rrdr_rows(rrdr) - 1];
+
+       switch(format) {
+       case DATASOURCE_SSV:
+               out->contenttype = CT_TEXT_PLAIN;
+               rrdr2ssv(rrdr, out, options, "", " ", "");
+               break;
+
+       case DATASOURCE_SSV_COMMA:
+               out->contenttype = CT_TEXT_PLAIN;
+               rrdr2ssv(rrdr, out, options, "", ",", "");
+               break;
+
+       case DATASOURCE_JS_ARRAY:
+               out->contenttype = CT_APPLICATION_JSON;
+               rrdr2ssv(rrdr, out, options, "[", ",", "]");
+               break;
+
+       case DATASOURCE_CSV:
+               out->contenttype = CT_TEXT_PLAIN;
+               rrdr2csv(rrdr, out, options, "", ",", "\r\n");
+               break;
+
+       case DATASOURCE_TSV:
+               out->contenttype = CT_TEXT_PLAIN;
+               rrdr2csv(rrdr, out, options, "", "\t", "\r\n");
+               break;
+
+       case DATASOURCE_HTML:
+               out->contenttype = CT_TEXT_HTML;
+               buffer_strcat(out, "<html>\n<center><table border=\"0\" cellpadding=\"5\" cellspacing=\"5\">");
+               rrdr2csv(rrdr, out, options, "<tr><td>", "</td><td>", "</td></tr>\n");
+               buffer_strcat(out, "</table>\n</center>\n</html>\n");
+               break;
+
+       case DATASOURCE_GOOGLE_JSONP:
+               out->contenttype = CT_APPLICATION_X_JAVASCRIPT;
+               rrdr2json(rrdr, out, options, 1);
+               break;
+
+       case DATASOURCE_GOOGLE_JSON:
+               out->contenttype = CT_APPLICATION_JSON;
+               rrdr2json(rrdr, out, options, 1);
+               break;
+
+       case DATASOURCE_JSONP:
+               out->contenttype = CT_APPLICATION_X_JAVASCRIPT;
+               rrdr2json(rrdr, out, options, 0);
+               break;
+
+       case DATASOURCE_JSON:
+       default:
+               out->contenttype = CT_APPLICATION_JSON;
+               rrdr2json(rrdr, out, options, 0);
+               break;
+       }
+
+       rrdr_free(rrdr);
+       return 200;
 }
 
 unsigned long rrd_stats_json(int type, RRDSET *st, BUFFER *wb, int points, int group, int group_method, time_t after, time_t before, int only_non_zero)
@@ -816,9 +1254,6 @@ unsigned long rrd_stats_json(int type, RRDSET *st, BUFFER *wb, int points, int g
                int annotate_reset = 0;
                int annotation_count = 0;
 
-               // the minimum line length we expect
-               int line_size = 4096 + (dimensions * 200);
-
                long    t = rrdset_time2slot(st, before),
                                stop_at_t = rrdset_time2slot(st, after),
                                stop_now = 0;
index d0dad28e09f7e620663505d5c257f0680ef10e7c..65a98a914d69f4aafbd0f1105951ac53168b3917 100755 (executable)
@@ -16,11 +16,24 @@ extern char *hostname;
 #define DATASOURCE_GOOGLE_JSONP 2
 #define DATASOURCE_SSV 3
 #define DATASOURCE_CSV 4
+#define DATASOURCE_JSONP 5
+#define DATASOURCE_TSV 6
+#define DATASOURCE_HTML 7
+#define DATASOURCE_JS_ARRAY 8
+#define DATASOURCE_SSV_COMMA 9
 
 #define GROUP_AVERAGE  0
 #define GROUP_MAX              1
 #define GROUP_SUM              2
 
+#define RRDR_OPTION_NONZERO            0x00000001 // don't output dimensions will just zero values
+#define RRDR_OPTION_REVERSED           0x00000002 // output the rows in reverse order (oldest to newest)
+#define RRDR_OPTION_ABSOLUTE           0x00000004 // values positive, for DATASOURCE_SSV before summing
+#define RRDR_OPTION_MIN2MAX                    0x00000008 // for DATASOURCE_SSV, out max - min
+#define RRDR_OPTION_SECONDS                    0x00000010 // output seconds, instead of dates
+#define RRDR_OPTION_MILLISECONDS       0x00000020 // output milliseconds, instead of dates
+#define RRDR_OPTION_NULL2ZERO          0x00000040 // do not show nulls, convert them to zeros
+
 extern void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb);
 extern void rrd_stats_api_v1_charts(BUFFER *wb);
 
@@ -32,4 +45,7 @@ extern void rrd_stats_all_json(BUFFER *wb);
 
 extern unsigned long rrd_stats_json(int type, RRDSET *st, BUFFER *wb, int entries_to_show, int group, int group_method, time_t after, time_t before, int only_non_zero);
 
+extern int rrd2format(RRDSET *st, BUFFER *out, BUFFER *dimensions, uint32_t format, long points, long long after, long long before, int group_method, uint32_t options, time_t *latest_timestamp);
+
+
 #endif /* NETDATA_RRD2JSON_H */
index 1607696cf9d9e54150133b3fbd16c2a9188884eb..db350ea3836dd9e404004e66cdba9741236bb1c4 100755 (executable)
@@ -71,6 +71,8 @@ void buffer_char_replace(BUFFER *wb, char from, char to)
 
 void buffer_strcat(BUFFER *wb, const char *txt)
 {
+       if(unlikely(!txt || !*txt)) return;
+
        if(wb->size - wb->len < 512)
                buffer_need_bytes(wb, 512);
 
@@ -102,6 +104,8 @@ void buffer_strcat(BUFFER *wb, const char *txt)
 
 void buffer_snprintf(BUFFER *wb, size_t len, const char *fmt, ...)
 {
+       if(unlikely(!fmt || !*fmt)) return;
+
        buffer_need_bytes(wb, len+1);
 
        va_list args;
@@ -116,6 +120,8 @@ void buffer_snprintf(BUFFER *wb, size_t len, const char *fmt, ...)
 
 void buffer_vsprintf(BUFFER *wb, const char *fmt, va_list args)
 {
+       if(unlikely(!fmt || !*fmt)) return;
+
        size_t len = wb->size - wb->len;
        if(unlikely(!len)) return;
 
@@ -128,6 +134,8 @@ void buffer_vsprintf(BUFFER *wb, const char *fmt, va_list args)
 
 void buffer_sprintf(BUFFER *wb, const char *fmt, ...)
 {
+       if(unlikely(!fmt || !*fmt)) return;
+
        if(unlikely(wb->len > wb->size)) {
                error("web_buffer_sprintf(): already overflown length %ld, size = %ld", wb->len, wb->size);
        }
@@ -135,7 +143,7 @@ void buffer_sprintf(BUFFER *wb, const char *fmt, ...)
 //     if(wb->size - wb->len < 512)
 //             web_buffer_need_bytes(wb, 512);
 
-       size_t len = wb->size - wb->len, old_len = wb->len, wrote;
+       size_t len = wb->size - wb->len, wrote;
 
        buffer_need_bytes(wb, len);
 
@@ -183,9 +191,9 @@ void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minu
 {
        //         10        20        30      = 35
        // 01234567890123456789012345678901234
-       // Date(2014, 04, 01, 03, 28, 20, 065)
+       // Date(2014,04,01,03,28,20)
 
-       buffer_need_bytes(wb, 36);
+       buffer_need_bytes(wb, 30);
 
        char *b = &wb->buffer[wb->len];
 
@@ -200,23 +208,18 @@ void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minu
        b[i++]= 48 + year / 10;
        b[i++]= 48 + year % 10;
        b[i++]=',';
-       //b[i++]=' ';
        b[i]= 48 + month / 10; if(b[i] != '0') i++;
        b[i++]= 48 + month % 10;
        b[i++]=',';
-       //b[i++]=' ';
        b[i]= 48 + day / 10; if(b[i] != '0') i++;
        b[i++]= 48 + day % 10;
        b[i++]=',';
-       //b[i++]=' ';
        b[i]= 48 + hours / 10; if(b[i] != '0') i++;
        b[i++]= 48 + hours % 10;
        b[i++]=',';
-       //b[i++]=' ';
        b[i]= 48 + minutes / 10; if(b[i] != '0') i++;
        b[i++]= 48 + minutes % 10;
        b[i++]=',';
-       //b[i++]=' ';
        b[i]= 48 + seconds / 10; if(b[i] != '0') i++;
        b[i++]= 48 + seconds % 10;
        b[i++]=')';
@@ -225,9 +228,47 @@ void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minu
        wb->len += i;
 
        // terminate it
-       buffer_need_bytes(wb, 1);
        wb->buffer[wb->len] = '\0';
+       buffer_overflow_check(wb);
+}
+
+// generate a date, the fastest possible way...
+void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds)
+{
+       //         10        20        30      = 35
+       // 01234567890123456789012345678901234
+       // 2014-04-01 03:28:20
+
+       buffer_need_bytes(wb, 36);
+
+       char *b = &wb->buffer[wb->len];
 
+       int i = 0;
+       b[i++]= 48 + year / 1000; year -= (year / 1000) * 1000;
+       b[i++]= 48 + year / 100; year -= (year / 100) * 100;
+       b[i++]= 48 + year / 10;
+       b[i++]= 48 + year % 10;
+       b[i++]='-';
+       b[i++]= 48 + month / 10;
+       b[i++]= 48 + month % 10;
+       b[i++]='-';
+       b[i++]= 48 + day / 10;
+       b[i++]= 48 + day % 10;
+       b[i++]=' ';
+       b[i++]= 48 + hours / 10;
+       b[i++]= 48 + hours % 10;
+       b[i++]=':';
+       b[i++]= 48 + minutes / 10;
+       b[i++]= 48 + minutes % 10;
+       b[i++]=':';
+       b[i++]= 48 + seconds / 10;
+       b[i++]= 48 + seconds % 10;
+       b[i]='\0';
+
+       wb->len += i;
+
+       // terminate it
+       wb->buffer[wb->len] = '\0';
        buffer_overflow_check(wb);
 }
 
index af5239437c7cc0d8e21d9a16a0d6013c4e7bdf01..03c2849e59fba52965865fb200494ac01dff4680 100755 (executable)
@@ -34,7 +34,7 @@ typedef struct web_buffer {
 #define buffer_strlen(wb) ((wb)->len)
 extern const char *buffer_tostring(BUFFER *wb);
 
-#define buffer_need_bytes(buffer, needed_free_size) do { if(unlikely((buffer)->size - (buffer)->len < (needed_free_size))) buffer_increase((buffer), (needed_free_size)); } while(0)
+#define buffer_need_bytes(buffer, needed_free_size) do { if(unlikely((buffer)->size - (buffer)->len < (long)(needed_free_size))) buffer_increase((buffer), (long)(needed_free_size)); } while(0)
 
 #define buffer_flush(wb) wb->buffer[wb->len = 0] = '\0'
 extern void buffer_reset(BUFFER *wb);
@@ -42,6 +42,7 @@ extern void buffer_reset(BUFFER *wb);
 extern void buffer_strcat(BUFFER *wb, const char *txt);
 extern void buffer_rrd_value(BUFFER *wb, calculated_number value);
 
+extern void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds);
 extern void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds);
 
 extern BUFFER *buffer_create(long size);
index c2e2854f0b1ac8e663f33757fe35bd15b8d795ab..6b5184c67ff74d0c54f6e907e69537f4f4c02301 100755 (executable)
@@ -14,6 +14,7 @@
 #include <netinet/tcp.h>
 #include <malloc.h>
 #include <pwd.h>
+#include <ctype.h>
 
 #include "common.h"
 #include "log.h"
@@ -398,9 +399,31 @@ void web_client_enable_deflate(struct web_client *w) {
 }
 #endif // NETDATA_WITH_ZLIB
 
-int rrdset_validate_dimensions(RRDSET *st, BUFFER *dimensions)
+uint32_t web_client_api_request_v1_data_options(char *o)
 {
-       ;
+       uint32_t ret = 0x00000000;
+       char *tok;
+
+       while(o && *o && (tok = mystrsep(&o, ", |"))) {
+               if(!*tok) continue;
+
+               if(!strcmp(tok, "nonzero"))
+                       ret |= RRDR_OPTION_NONZERO;
+               else if(!strcmp(tok, "flip") || !strcmp(tok, "reversed") || !strcmp(tok, "reverse"))
+                       ret |= RRDR_OPTION_REVERSED;
+               else if(!strcmp(tok, "abs") || !strcmp(tok, "absolute") || !strcmp(tok, "absolute_sum") || !strcmp(tok, "absolute-sum"))
+                       ret |= RRDR_OPTION_ABSOLUTE;
+               else if(!strcmp(tok, "min2max"))
+                       ret |= RRDR_OPTION_MIN2MAX;
+               else if(!strcmp(tok, "seconds"))
+                       ret |= RRDR_OPTION_SECONDS;
+               else if(!strcmp(tok, "ms") || !strcmp(tok, "milliseconds"))
+                       ret |= RRDR_OPTION_MILLISECONDS;
+               else if(!strcmp(tok, "null2zero"))
+                       ret |= RRDR_OPTION_NULL2ZERO;
+       }
+
+       return ret;
 }
 
 int web_client_api_request_v1_data_format(char *name)
@@ -414,17 +437,65 @@ int web_client_api_request_v1_data_format(char *name)
        else if(!strcmp(name, "json"))
                return DATASOURCE_JSON;
 
+       else if(!strcmp(name, "jsonp"))
+               return DATASOURCE_JSONP;
+
        else if(!strcmp(name, "ssv"))
                return DATASOURCE_SSV;
 
        else if(!strcmp(name, "csv"))
                return DATASOURCE_CSV;
 
-       return DATASOURCE_INVALID;
+       else if(!strcmp(name, "tsv"))
+               return DATASOURCE_TSV;
+
+       else if(!strcmp(name, "tsv-excel"))
+               return DATASOURCE_TSV;
+
+       else if(!strcmp(name, "html"))
+               return DATASOURCE_HTML;
+
+       else if(!strcmp(name, "array"))
+               return DATASOURCE_JS_ARRAY;
+
+       else if(!strcmp(name, "ssvcomma"))
+               return DATASOURCE_SSV_COMMA;
+
+       return DATASOURCE_JSON;
+}
+
+int web_client_api_request_v1_data_google_format(char *name)
+{
+       if(!strcmp(name, "json"))
+               return DATASOURCE_GOOGLE_JSONP;
+
+       else if(!strcmp(name, "html"))
+               return DATASOURCE_HTML;
+
+       else if(!strcmp(name, "csv"))
+               return DATASOURCE_CSV;
+
+       else if(!strcmp(name, "tsv-excel"))
+               return DATASOURCE_TSV;
+
+       return DATASOURCE_JSON;
+}
+
+int web_client_api_request_v1_data_group(char *name)
+{
+       if(!strcmp(name, "max"))
+               return GROUP_MAX;
+
+       else if(!strcmp(name, "average"))
+               return GROUP_AVERAGE;
+
+       return GROUP_MAX;
 }
 
 int web_client_api_request_v1_charts(struct web_client *w, char *url)
 {
+       if(url) { ; }
+
        buffer_flush(w->response.data);
        rrd_stats_api_v1_charts(w->response.data);
        return 200;
@@ -476,22 +547,32 @@ cleanup:
        return ret;
 }
 
+// returns the HTTP code
 int web_client_api_request_v1_data(struct web_client *w, char *url)
 {
+       debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
+
        int ret = 400;
        BUFFER *dimensions = NULL;
 
        buffer_flush(w->response.data);
 
+       char    *google_version = "0.6",
+                       *google_reqId = "0",
+                       *google_sig = "0",
+                       *google_out = "json",
+                       *google_responseHandler = "google.visualization.Query.setResponse",
+                       *google_outFileName = NULL;
+
+       time_t last_timestamp_in_data = 0, google_timestamp = 0;
+
        char *chart = NULL
-                       , *dim = NULL
                        , *before_str = NULL
                        , *after_str = NULL
-                       , *points_str = NULL
-                       , *group_str = NULL
-                       , *options = NULL;
+                       , *points_str = NULL;
 
-       int format = DATASOURCE_JSON;
+       int format = DATASOURCE_JSON, group = GROUP_MAX;
+       uint32_t options = 0x00000000;
 
        while(url) {
                char *value = mystrsep(&url, "?&[]");
@@ -501,11 +582,13 @@ int web_client_api_request_v1_data(struct web_client *w, char *url)
                if(!name || !*name) continue;
                if(!value || !*value) continue;
 
+               debug(D_WEB_CLIENT, "%llu: API v1 query param '%s' with value '%s'", w->id, name, value);
+
                // name and value are now the parameters
                // they are not null and not empty
 
                if(!strcmp(name, "chart")) chart = value;
-               else if(!strcmp(name, "dimension") || !strcmp(name, "dim")) {
+               else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
                        if(!dimensions) dimensions = buffer_create(strlen(value));
                        if(dimensions) {
                                buffer_strcat(dimensions, "|");
@@ -515,16 +598,45 @@ int web_client_api_request_v1_data(struct web_client *w, char *url)
                else if(!strcmp(name, "after")) after_str = value;
                else if(!strcmp(name, "before")) before_str = value;
                else if(!strcmp(name, "points")) points_str = value;
-               else if(!strcmp(name, "group")) group_str = value;
+               else if(!strcmp(name, "group")) {
+                       group = web_client_api_request_v1_data_group(value);
+               }
                else if(!strcmp(name, "format")) {
                        format = web_client_api_request_v1_data_format(value);
                }
                else if(!strcmp(name, "options")) {
-                       options = value;
+                       options |= web_client_api_request_v1_data_options(value);
                }
-               else {
-                       buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
-                       goto cleanup;
+               else if(!strcmp(name, "tqx")) {
+                       // parse Google Visualization API options
+                       // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
+                       char *tqx_name, *tqx_value;
+
+                       while(value) {
+                               tqx_value = mystrsep(&value, ";");
+                               if(!tqx_value || !*tqx_value) continue;
+
+                               tqx_name = mystrsep(&tqx_value, ":");
+                               if(!tqx_name || !*tqx_name) continue;
+                               if(!tqx_value || !*tqx_value) continue;
+
+                               if(!strcmp(tqx_name, "version"))
+                                       google_version = tqx_value;
+                               else if(!strcmp(tqx_name, "reqId"))
+                                       google_reqId = tqx_value;
+                               else if(!strcmp(tqx_name, "sig")) {
+                                       google_sig = tqx_value;
+                                       google_timestamp = strtoul(google_sig, NULL, 0);
+                               }
+                               else if(!strcmp(tqx_name, "out")) {
+                                       google_out = tqx_value;
+                                       format = web_client_api_request_v1_data_google_format(google_out);
+                               }
+                               else if(!strcmp(tqx_name, "responseHandler"))
+                                       google_responseHandler = tqx_value;
+                               else if(!strcmp(tqx_name, "outFileName"))
+                                       google_outFileName = tqx_value;
+                       }
                }
        }
 
@@ -541,12 +653,51 @@ int web_client_api_request_v1_data(struct web_client *w, char *url)
                goto cleanup;
        }
 
-       uint32_t before = (before_str && *before_str)?atol(before_str):0;
-       uint32_t after  = (after_str  && *after_str) ?atol(after_str):0;
-       int      points = (points_str && *points_str)?atoi(points_str):0;
-       int      group  = (group_str  && *group_str) ?atoi(group_str):0;
+       long long before = (before_str && *before_str)?atol(before_str):0;
+       long long after  = (after_str  && *after_str) ?atol(after_str):0;
+       int       points = (points_str && *points_str)?atoi(points_str):0;
+
+       debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%u', format '%u', options '0x%08x'"
+                       , w->id
+                       , chart
+                       , (dimensions)?buffer_tostring(dimensions):""
+                       , after
+                       , before
+                       , points
+                       , group
+                       , format
+                       , options
+                       );
+
+       if(google_outFileName && *google_outFileName) {
+               buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", google_outFileName);
+               error("generating outfilename header: '%s'", google_outFileName);
+       }
+
+       if(format == DATASOURCE_GOOGLE_JSONP) {
+               debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
+                               w->id, google_version, google_reqId, google_sig, google_out, google_responseHandler, google_outFileName
+                       );
+
+               buffer_sprintf(w->response.data,
+                       "%s({version:'%s',reqId:'%s',status:'ok',sig:'%lu',table:",
+                       google_responseHandler, google_version, google_reqId, st->last_updated.tv_sec);
+       }
+
+       ret = rrd2format(st, w->response.data, dimensions, format, points, after, before, group, options, &last_timestamp_in_data);
 
-       buffer_sprintf(w->response.data, "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);
+       if(format == DATASOURCE_GOOGLE_JSONP) {
+               if(google_timestamp < last_timestamp_in_data)
+                       buffer_strcat(w->response.data, "});");
+
+               else {
+                       // the client already has the latest data
+                       buffer_flush(w->response.data);
+                       buffer_sprintf(w->response.data,
+                               "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
+                               google_responseHandler, google_version, google_reqId);
+               }
+       }
 
 cleanup:
        if(dimensions) buffer_free(dimensions);
@@ -732,6 +883,7 @@ int web_client_data_request(struct web_client *w, char *url, int datasource_type
 
                else {
                        // the client already has the latest data
+                       buffer_flush(w->response.data);
                        buffer_sprintf(w->response.data,
                                "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
                                google_responseHandler, google_version, google_reqId);