]> arthur.barton.de Git - netdata.git/commitdiff
added support for generating SVG badges with chart data
authorCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Sat, 4 Jun 2016 12:58:03 +0000 (15:58 +0300)
committerCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Sat, 4 Jun 2016 12:58:03 +0000 (15:58 +0300)
.gitignore
src/Makefile.am
src/rrd2json.c
src/rrd2json.h
src/web_buffer_svg.c [new file with mode: 0644]
src/web_buffer_svg.h [new file with mode: 0644]
src/web_client.c
src/web_client.h

index 02801b7784839bf0c9435994d3e108b41b7b86ba..a4c1644f61660685aa94d379ea99132b68891ddf 100644 (file)
@@ -58,6 +58,7 @@ web/gadget.xml
 web/index_new.html
 web/version.txt
 
+system/netdata-lsb
 system/netdata-openrc
 system/netdata-init-d
 system/netdata.logrotate
index e9fc8f332e7f0c439019055fa64457e34adb1870..ac7ac44f5d33484ddd51216b727da3051e3d1607 100644 (file)
@@ -67,6 +67,7 @@ netdata_SOURCES = \
        unit_test.c unit_test.h \
        url.c url.h \
        web_buffer.c web_buffer.h \
+       web_buffer_svg.c web_buffer_svg.h \
        web_client.c web_client.h \
        web_server.c web_server.h \
        $(NULL)
index e0bd066707ad3d355a497fbb0c565fab8100d8b8..5adaab05b3c71d5c3a6b0bf6dd687b4e360a5bf5 100644 (file)
@@ -985,104 +985,121 @@ static void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t options, const char *startlin
        //info("RRD2CSV(): %s: END", r->st->id);
 }
 
-static void rrdr2ssv(RRDR *r, BUFFER *wb, uint32_t options, const char *prefix, const char *separator, const char *suffix)
-{
-       //info("RRD2SSV(): %s: BEGIN", r->st->id);
-       long c, i;
+inline static calculated_number rrdr2value(RRDR *r, long i, uint32_t options, int *all_values_are_null) {
+       long c;
        RRDDIM *d;
 
-       buffer_strcat(wb, prefix);
-       long start = 0, end = rrdr_rows(r), step = 1;
-       if((options & RRDR_OPTION_REVERSED)) {
-               start = rrdr_rows(r) - 1;
-               end = -1;
-               step = -1;
-       }
+       calculated_number *cn = &r->v[ i * r->d ];
+       uint8_t *co = &r->o[ i * r->d ];
+
+       calculated_number sum = 0, min = 0, max = 0, v;
+       int all_null = 1, init = 1;
 
-       // for each line in the array
        calculated_number total = 1;
-       for(i = start; i != end ;i += step) {
+       if(unlikely(options & RRDR_OPTION_PERCENTAGE)) {
+               total = 0;
+               for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) {
+                       calculated_number n = cn[c];
 
-               calculated_number *cn = &r->v[ i * r->d ];
-               uint8_t *co = &r->o[ i * r->d ];
+                       if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
+                               n = -n;
 
-               calculated_number sum = 0, min = 0, max = 0, v;
-               int all_null = 1, init = 1;
+                       total += n;
+               }
+               // prevent a division by zero
+               if(total == 0) total = 1;
+       }
 
-               if(unlikely(options & RRDR_OPTION_PERCENTAGE)) {
-                       total = 0;
-                       for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) {
-                               calculated_number n = cn[c];
+       // for each dimension
+       for(c = 0, d = r->st->dimensions; d && c < r->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(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
-                                       n = -n;
+               calculated_number n = cn[c];
 
-                               total += n;
+               if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
+                       n = -n;
+
+               if(unlikely(options & RRDR_OPTION_PERCENTAGE))
+                       n = n * 100 / total;
+
+               if(unlikely(init)) {
+                       if(n > 0) {
+                               min = 0;
+                               max = n;
                        }
-                       // prevent a division by zero
-                       if(total == 0) total = 1;
+                       else {
+                               min = n;
+                               max = 0;
+                       }
+                       init = 0;
                }
 
-               // for each dimension
-               for(c = 0, d = r->st->dimensions; d && c < r->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(likely(!(co[c] & RRDR_EMPTY))) {
+                       all_null = 0;
+                       sum += n;
+               }
 
-                       calculated_number n = cn[c];
+               if(n < min) min = n;
+               if(n > max) max = n;
+       }
 
-                       if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
-                               n = -n;
+       if(unlikely(all_null)) {
+               if(likely(*all_values_are_null))
+                       *all_values_are_null = 1;
+               return 0;
+       }
+       else {
+               if(likely(*all_values_are_null))
+                       *all_values_are_null = 0;
+       }
 
-                       if(unlikely(options & RRDR_OPTION_PERCENTAGE))
-                               n = n * 100 / total;
+       if(options & RRDR_OPTION_MIN2MAX)
+               v = max - min;
+       else
+               v = sum;
 
-                       if(unlikely(init)) {
-                               if(n > 0) {
-                                       min = 0;
-                                       max = n;
-                               }
-                               else {
-                                       min = n;
-                                       max = 0;
-                               }
-                               init = 0;
-                       }
+       return v;
+}
 
-                       if(likely(!(co[c] & RRDR_EMPTY))) {
-                               all_null = 0;
-                               sum += n;
-                       }
+static void rrdr2ssv(RRDR *r, BUFFER *wb, uint32_t options, const char *prefix, const char *separator, const char *suffix)
+{
+       //info("RRD2SSV(): %s: BEGIN", r->st->id);
+       long i;
 
-                       if(n < min) min = n;
-                       if(n > max) max = n;
+       buffer_strcat(wb, 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) {
+               int all_values_are_null = 0;
+               calculated_number v = rrdr2value(r, i, options, &all_values_are_null);
+
+               if(likely(i != start)) {
+                       if(r->min > v) r->min = v;
+                       if(r->max < v) r->max = v;
+               }
+               else {
+                       r->min = v;
+                       r->max = v;
                }
 
                if(likely(i != start))
                        buffer_strcat(wb, separator);
 
-               if(all_null) {
+               if(all_values_are_null) {
                        if(options & RRDR_OPTION_NULL2ZERO)
                                buffer_strcat(wb, "0");
                        else
                                buffer_strcat(wb, "null");
                }
-               else {
-                       if(options & RRDR_OPTION_MIN2MAX)
-                               v = max - min;
-                       else
-                               v = sum;
-
-                       if(likely(i != start)) {
-                               if(r->min > v) r->min = v;
-                               if(r->max < v) r->max = v;
-                       }
-                       else {
-                               r->min = v;
-                               r->max = v;
-                       }
-
+               else
                        buffer_rrd_value(wb, v);
-               }
        }
        buffer_strcat(wb, suffix);
        //info("RRD2SSV(): %s: END", r->st->id);
@@ -1513,6 +1530,40 @@ RRDR *rrd2rrdr(RRDSET *st, long points, long long after, long long before, int g
        return r;
 }
 
+int rrd2value(RRDSET *st, BUFFER *wb, calculated_number *n, BUFFER *dimensions, long points, long long after, long long before, int group_method, uint32_t options, time_t *latest_timestamp, int *value_is_null)
+{
+       RRDR *r = rrd2rrdr(st, points, after, before, group_method);
+       if(!r) {
+               if(value_is_null) *value_is_null = 1;
+               return 500;
+       }
+
+       if(rrdr_rows(r) == 0) {
+               rrdr_free(r);
+               if(value_is_null) *value_is_null = 1;
+               return 400;
+       }
+
+       if(r->result_options & RRDR_RESULT_OPTION_RELATIVE)
+               wb->options |= WB_CONTENT_NO_CACHEABLE;
+       else if(r->result_options & RRDR_RESULT_OPTION_ABSOLUTE)
+               wb->options |= WB_CONTENT_CACHEABLE;
+
+       options = rrdr_check_options(r, options, (dimensions)?buffer_tostring(dimensions):NULL);
+
+       if(dimensions)
+               rrdr_disable_not_selected_dimensions(r, buffer_tostring(dimensions));
+
+       if(latest_timestamp)
+               *latest_timestamp = r->before;
+
+       long i = (options & RRDR_OPTION_REVERSED)?rrdr_rows(r) - 1:0;
+       *n = rrdr2value(r, i, options, value_is_null);
+
+       rrdr_free(r);
+       return 200;
+}
+
 int rrd2format(RRDSET *st, BUFFER *wb, BUFFER *dimensions, uint32_t format, long points, long long after, long long before, int group_method, uint32_t options, time_t *latest_timestamp)
 {
        RRDR *r = rrd2rrdr(st, points, after, before, group_method);
index d1f850d4ac073980393f9c46bc81f7c7cc7d540c..6ba9fb7fda9b4799a3ec05c84da22166262095aa 100644 (file)
@@ -64,6 +64,6 @@ extern void rrd_stats_all_json(BUFFER *wb);
 extern time_t rrd_stats_json(int type, RRDSET *st, BUFFER *wb, long entries_to_show, long 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);
-
+extern int rrd2value(RRDSET *st, BUFFER *wb, calculated_number *n, BUFFER *dimensions, long points, long long after, long long before, int group_method, uint32_t options, time_t *latest_timestamp, int *value_is_null);
 
 #endif /* NETDATA_RRD2JSON_H */
diff --git a/src/web_buffer_svg.c b/src/web_buffer_svg.c
new file mode 100644 (file)
index 0000000..3c342e0
--- /dev/null
@@ -0,0 +1,569 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <math.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "common.h"
+#include "log.h"
+#include "appconfig.h"
+#include "url.h"
+#include "web_buffer.h"
+#include "web_buffer_svg.h"
+
+#define BADGE_HORIZONTAL_PADDING 4
+#define VERDANA_KERNING 0.5
+
+/*
+ * verdana11_widths[] has been generated with this method:
+ * https://github.com/badges/shields/blob/master/measure-text.js
+*/
+
+double verdana11_widths[256] = {
+       [0] = 0.0,
+       [1] = 0.0,
+       [2] = 0.0,
+       [3] = 0.0,
+       [4] = 0.0,
+       [5] = 0.0,
+       [6] = 0.0,
+       [7] = 0.0,
+       [8] = 0.0,
+       [9] = 0.0,
+       [10] = 0.0,
+       [11] = 0.0,
+       [12] = 0.0,
+       [13] = 0.0,
+       [14] = 0.0,
+       [15] = 0.0,
+       [16] = 0.0,
+       [17] = 0.0,
+       [18] = 0.0,
+       [19] = 0.0,
+       [20] = 0.0,
+       [21] = 0.0,
+       [22] = 0.0,
+       [23] = 0.0,
+       [24] = 0.0,
+       [25] = 0.0,
+       [26] = 0.0,
+       [27] = 0.0,
+       [28] = 0.0,
+       [29] = 0.0,
+       [30] = 0.0,
+       [31] = 0.0,
+       [32] = 3.8671874999999996, //
+       [33] = 4.3291015625, // !
+       [34] = 5.048828125, // "
+       [35] = 9.001953125, // #
+       [36] = 6.9931640625, // $
+       [37] = 11.837890625, // %
+       [38] = 7.992187499999999, // &
+       [39] = 2.9541015625, // '
+       [40] = 4.9951171875, // (
+       [41] = 4.9951171875, // )
+       [42] = 6.9931640625, // *
+       [43] = 9.001953125, // +
+       [44] = 4.00146484375, // ,
+       [45] = 4.9951171875, // -
+       [46] = 4.00146484375, // .
+       [47] = 4.9951171875, // /
+       [48] = 6.9931640625, // 0
+       [49] = 6.9931640625, // 1
+       [50] = 6.9931640625, // 2
+       [51] = 6.9931640625, // 3
+       [52] = 6.9931640625, // 4
+       [53] = 6.9931640625, // 5
+       [54] = 6.9931640625, // 6
+       [55] = 6.9931640625, // 7
+       [56] = 6.9931640625, // 8
+       [57] = 6.9931640625, // 9
+       [58] = 4.9951171875, // :
+       [59] = 4.9951171875, // ;
+       [60] = 9.001953125, // <
+       [61] = 9.001953125, // =
+       [62] = 9.001953125, // >
+       [63] = 5.99951171875, // ?
+       [64] = 11.0, // @
+       [65] = 7.51953125, // A
+       [66] = 7.541015625, // B
+       [67] = 7.680664062499999, // C
+       [68] = 8.4755859375, // D
+       [69] = 6.95556640625, // E
+       [70] = 6.32177734375, // F
+       [71] = 8.529296875, // G
+       [72] = 8.26611328125, // H
+       [73] = 4.6298828125, // I
+       [74] = 5.00048828125, // J
+       [75] = 7.62158203125, // K
+       [76] = 6.123046875, // L
+       [77] = 9.2705078125, // M
+       [78] = 8.228515625, // N
+       [79] = 8.658203125, // O
+       [80] = 6.63330078125, // P
+       [81] = 8.658203125, // Q
+       [82] = 7.6484375, // R
+       [83] = 7.51953125, // S
+       [84] = 6.7783203125, // T
+       [85] = 8.05126953125, // U
+       [86] = 7.51953125, // V
+       [87] = 10.87646484375, // W
+       [88] = 7.53564453125, // X
+       [89] = 6.767578125, // Y
+       [90] = 7.53564453125, // Z
+       [91] = 4.9951171875, // [
+       [92] = 4.9951171875, // backslash
+       [93] = 4.9951171875, // ]
+       [94] = 9.001953125, // ^
+       [95] = 6.9931640625, // _
+       [96] = 6.9931640625, // `
+       [97] = 6.6064453125, // a
+       [98] = 6.853515625, // b
+       [99] = 5.73095703125, // c
+       [100] = 6.853515625, // d
+       [101] = 6.552734375, // e
+       [102] = 3.8671874999999996, // f
+       [103] = 6.853515625, // g
+       [104] = 6.9609375, // h
+       [105] = 3.0185546875, // i
+       [106] = 3.78662109375, // j
+       [107] = 6.509765625, // k
+       [108] = 3.0185546875, // l
+       [109] = 10.69921875, // m
+       [110] = 6.9609375, // n
+       [111] = 6.67626953125, // o
+       [112] = 6.853515625, // p
+       [113] = 6.853515625, // q
+       [114] = 4.6943359375, // r
+       [115] = 5.73095703125, // s
+       [116] = 4.33447265625, // t
+       [117] = 6.9609375, // u
+       [118] = 6.509765625, // v
+       [119] = 9.001953125, // w
+       [120] = 6.509765625, // x
+       [121] = 6.509765625, // y
+       [122] = 5.779296875, // z
+       [123] = 6.982421875, // {
+       [124] = 4.9951171875, // |
+       [125] = 6.982421875, // }
+       [126] = 9.001953125, // ~
+       [127] = 0.0,
+       [128] = 0.0,
+       [129] = 0.0,
+       [130] = 0.0,
+       [131] = 0.0,
+       [132] = 0.0,
+       [133] = 0.0,
+       [134] = 0.0,
+       [135] = 0.0,
+       [136] = 0.0,
+       [137] = 0.0,
+       [138] = 0.0,
+       [139] = 0.0,
+       [140] = 0.0,
+       [141] = 0.0,
+       [142] = 0.0,
+       [143] = 0.0,
+       [144] = 0.0,
+       [145] = 0.0,
+       [146] = 0.0,
+       [147] = 0.0,
+       [148] = 0.0,
+       [149] = 0.0,
+       [150] = 0.0,
+       [151] = 0.0,
+       [152] = 0.0,
+       [153] = 0.0,
+       [154] = 0.0,
+       [155] = 0.0,
+       [156] = 0.0,
+       [157] = 0.0,
+       [158] = 0.0,
+       [159] = 0.0,
+       [160] = 0.0,
+       [161] = 0.0,
+       [162] = 0.0,
+       [163] = 0.0,
+       [164] = 0.0,
+       [165] = 0.0,
+       [166] = 0.0,
+       [167] = 0.0,
+       [168] = 0.0,
+       [169] = 0.0,
+       [170] = 0.0,
+       [171] = 0.0,
+       [172] = 0.0,
+       [173] = 0.0,
+       [174] = 0.0,
+       [175] = 0.0,
+       [176] = 0.0,
+       [177] = 0.0,
+       [178] = 0.0,
+       [179] = 0.0,
+       [180] = 0.0,
+       [181] = 0.0,
+       [182] = 0.0,
+       [183] = 0.0,
+       [184] = 0.0,
+       [185] = 0.0,
+       [186] = 0.0,
+       [187] = 0.0,
+       [188] = 0.0,
+       [189] = 0.0,
+       [190] = 0.0,
+       [191] = 0.0,
+       [192] = 0.0,
+       [193] = 0.0,
+       [194] = 0.0,
+       [195] = 0.0,
+       [196] = 0.0,
+       [197] = 0.0,
+       [198] = 0.0,
+       [199] = 0.0,
+       [200] = 0.0,
+       [201] = 0.0,
+       [202] = 0.0,
+       [203] = 0.0,
+       [204] = 0.0,
+       [205] = 0.0,
+       [206] = 0.0,
+       [207] = 0.0,
+       [208] = 0.0,
+       [209] = 0.0,
+       [210] = 0.0,
+       [211] = 0.0,
+       [212] = 0.0,
+       [213] = 0.0,
+       [214] = 0.0,
+       [215] = 0.0,
+       [216] = 0.0,
+       [217] = 0.0,
+       [218] = 0.0,
+       [219] = 0.0,
+       [220] = 0.0,
+       [221] = 0.0,
+       [222] = 0.0,
+       [223] = 0.0,
+       [224] = 0.0,
+       [225] = 0.0,
+       [226] = 0.0,
+       [227] = 0.0,
+       [228] = 0.0,
+       [229] = 0.0,
+       [230] = 0.0,
+       [231] = 0.0,
+       [232] = 0.0,
+       [233] = 0.0,
+       [234] = 0.0,
+       [235] = 0.0,
+       [236] = 0.0,
+       [237] = 0.0,
+       [238] = 0.0,
+       [239] = 0.0,
+       [240] = 0.0,
+       [241] = 0.0,
+       [242] = 0.0,
+       [243] = 0.0,
+       [244] = 0.0,
+       [245] = 0.0,
+       [246] = 0.0,
+       [247] = 0.0,
+       [248] = 0.0,
+       [249] = 0.0,
+       [250] = 0.0,
+       [251] = 0.0,
+       [252] = 0.0,
+       [253] = 0.0,
+       [254] = 0.0,
+       [255] = 0.0
+};
+
+// find the width of the string using the verdana 11points font
+// re-write the string in place, skiping zero-length characters
+static inline int verdana11_width(char *s) {
+       double w = 0.0;
+       char *d = s;
+
+       while(*s) {
+               double t = verdana11_widths[(unsigned char)*s];
+               if(t == 0.0)
+                       s++;
+               else {
+                       w += t + VERDANA_KERNING;
+                       if(d != s)
+                               *d++ = *s++;
+                       else
+                               d = ++s;
+               }
+       }
+
+       *d = '\0';
+       w -= VERDANA_KERNING;
+       return ceil(w);
+}
+
+static inline size_t escape_xmlz(char *dst, const char *src, size_t len) {
+       size_t i = len;
+
+       // required escapes from
+       // https://github.com/badges/shields/blob/master/badge.js
+       while(*src && i) {
+               switch(*src) {
+                       case '&':
+                               if(i > 5) {
+                                       strcpy(dst, "&amp;");
+                                       i -= 5;
+                                       dst += 5;
+                                       src++;
+                               }
+                               else goto cleanup;
+                               break;
+
+                       case '<':
+                               if(i > 4) {
+                                       strcpy(dst, "&lt;");
+                                       i -= 4;
+                                       dst += 4;
+                                       src++;
+                               }
+                               else goto cleanup;
+                               break;
+
+                       case '>':
+                               if(i > 4) {
+                                       strcpy(dst, "&gt;");
+                                       i -= 4;
+                                       dst += 4;
+                                       src++;
+                               }
+                               else goto cleanup;
+                               break;
+
+                       case '"':
+                               if(i > 6) {
+                                       strcpy(dst, "&quot;");
+                                       i -= 6;
+                                       dst += 6;
+                                       src++;
+                               }
+                               else goto cleanup;
+                               break;
+
+                       case '\'':
+                               if(i > 6) {
+                                       strcpy(dst, "&apos;");
+                                       i -= 6;
+                                       dst += 6;
+                                       src++;
+                               }
+                               else goto cleanup;
+                               break;
+
+                       default:
+                               i--;
+                               *dst++ = *src++;
+                               break;
+               }
+       }
+
+cleanup:
+       *dst = '\0';
+       return len - i;
+}
+
+static inline const char *fix_units(const char *units) {
+       if(!strcmp(units, "percentage") || !strcmp(units, "percent") || !strcmp(units, "pcent")) return "%";
+       return units;
+}
+
+static inline const char *color_map(const char *color) {
+       // colors from:
+       // https://github.com/badges/shields/blob/master/colorscheme.json
+            if(!strcmp(color, "brightgreen")) return "#4c1";
+       else if(!strcmp(color, "green"))       return "#97CA00";
+       else if(!strcmp(color, "yellow"))      return "#dfb317";
+       else if(!strcmp(color, "yellowgreen")) return "#a4a61d";
+       else if(!strcmp(color, "orange"))      return "#fe7d37";
+       else if(!strcmp(color, "red"))         return "#e05d44";
+       else if(!strcmp(color, "blue"))        return "#007ec6";
+       else if(!strcmp(color, "grey"))        return "#555";
+       else if(!strcmp(color, "gray"))        return "#555";
+       else if(!strcmp(color, "lightgrey"))   return "#9f9f9f";
+       else if(!strcmp(color, "lightgray"))   return "#9f9f9f";
+       return color;
+}
+
+static inline void calc_colorz(const char *color, char *final, size_t len, calculated_number value, int value_is_null) {
+       char color_buffer[256 + 1] = "";
+       char value_buffer[256 + 1] = "";
+       char comparison = '>';
+
+       // example input:
+       // color<max|color>min|color:null...
+
+       const char *c = color;
+       while(*c) {
+               char *dc = color_buffer, *dv = NULL;
+               size_t ci = 0, vi = 0;
+
+               const char *t = c;
+
+               while(*t && *t != '|') {
+                       switch(*t) {
+                               case ':':
+                                       comparison = '=';
+                                       dv = value_buffer;
+                                       break;
+
+                               case '}':
+                               case ')':
+                               case '>':
+                                       if(t[1] == '=') {
+                                               comparison = ')';
+                                               t++;
+                                       }
+                                       else
+                                               comparison = '>';
+                                       dv = value_buffer;
+                                       break;
+
+                               case '{':
+                               case '(':
+                               case '<':
+                                       if(t[1] == '=') {
+                                               comparison = '(';
+                                               t++;
+                                       }
+                                       else
+                                               comparison = '<';
+                                       dv = value_buffer;
+                                       break;
+
+                               default:
+                                       if(dv) {
+                                               if(vi < 256) {
+                                                       vi++;
+                                                       *dv++ = *t;
+                                               }
+                                       }
+                                       else {
+                                               if(ci < 256) {
+                                                       ci++;
+                                                       *dc++ = *t;
+                                               }
+                                       }
+                                       break;
+                       }
+
+                       t++;
+               }
+
+               // prepare for next iteration
+               if(*t == '|') t++;
+               c = t;
+
+               // do the math
+               *dc = '\0';
+               if(dv) {
+                       *dv = '\0';
+
+                       if(value_is_null) {
+                               if(!*value_buffer || !strcmp(value_buffer, "null"))
+                                       break;
+                       }
+                       else {
+                               calculated_number v = strtold(value_buffer, NULL);
+
+                                    if(comparison == '<' && value < v) break;
+                               else if(comparison == '(' && value <= v) break;
+                               else if(comparison == '>' && value > v) break;
+                               else if(comparison == ')' && value >= v) break;
+                               else if(comparison == '=' && value == v) break;
+                       }
+               }
+               else
+                       break;
+       }
+
+       const char *b;
+       if(color_buffer[0])
+               b = color_buffer;
+       else
+               b = color;
+
+       strncpyz(final, b, len);
+}
+
+void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const char *units, const char *label_color, const char *value_color, int value_is_null) {
+       char label_buffer[256 + 1], value_string[512 + 1], value_color_buffer[256 + 1];
+       char label_escaped[256 + 1], value_escaped[512 + 1], label_color_escaped[256 + 1], value_color_escaped[256 + 1];
+       int label_width, value_width, total_width;
+
+       if(!label_color || !*label_color) label_color = "#555";
+       if(!value_color || !*value_color) value_color = "#4c1";
+
+       units = fix_units(units);
+       calc_colorz(value_color, value_color_buffer, 256, value, value_is_null);
+
+       char *separator = "";
+       if(isalnum(*units)) separator = " ";
+
+       if(value_is_null)
+               strcpy(value_string, "-");
+       else {
+               calculated_number abs = (value < (calculated_number)0)?-value:value;
+               if(abs > (calculated_number)1000.0)      snprintfz(value_string, 512, "%0.0Lf%s%s", (long double)value, separator, units);
+               else if(abs > (calculated_number)100.0)  snprintfz(value_string, 512, "%0.1Lf%s%s", (long double)value, separator, units);
+               else if(abs > (calculated_number)1.0)    snprintfz(value_string, 512, "%0.2Lf%s%s", (long double)value, separator, units);
+               else if(abs > (calculated_number)0.1)    snprintfz(value_string, 512, "%0.3Lf%s%s", (long double)value, separator, units);
+               else                                     snprintfz(value_string, 512, "%0.4Lf%s%s", (long double)value, separator, units);
+       }
+
+       // we need to copy the label, since verdana11_width may write to it
+       strncpyz(label_buffer, label, 256);
+
+       label_width = verdana11_width(label_buffer) + (BADGE_HORIZONTAL_PADDING * 2);
+       value_width = verdana11_width(value_string) + (BADGE_HORIZONTAL_PADDING * 2);
+       total_width = label_width + value_width;
+
+       escape_xmlz(label_escaped, label_buffer, 256);
+       escape_xmlz(value_escaped, value_string, 256);
+       escape_xmlz(label_color_escaped, color_map(label_color), 256);
+       escape_xmlz(value_color_escaped, color_map(value_color_buffer), 256);
+
+       wb->contenttype = CT_IMAGE_SVG_XML;
+
+       // svg template from:
+       // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
+       buffer_sprintf(wb,
+               "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%zu\" height=\"20\">"
+                       "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">"
+                               "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>"
+                               "<stop offset=\"1\" stop-opacity=\".1\"/>"
+                       "</linearGradient>"
+                       "<mask id=\"round\">"
+                               "<rect width=\"%zu\" height=\"20\" rx=\"3\" fill=\"#fff\"/>"
+                       "</mask>"
+                       "<g mask=\"url(#round)\">"
+                               "<rect width=\"%zu\" height=\"20\" fill=\"%s\"/>"
+                               "<rect x=\"%zu\" width=\"%zu\" height=\"20\" fill=\"%s\"/>"
+                               "<rect width=\"%zu\" height=\"20\" fill=\"url(#smooth)\"/>"
+                       "</g>"
+                       "<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">"
+                               "<text x=\"%zu\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
+                               "<text x=\"%zu\" y=\"14\">%s</text>"
+                               "<text x=\"%zu\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
+                               "<text x=\"%zu\" y=\"14\">%s</text>"
+                       "</g>"
+               "</svg>",
+               total_width, total_width,
+               label_width, label_color_escaped,
+               label_width, value_width, value_color_escaped,
+               total_width,
+               label_width / 2, label_escaped,
+               label_width / 2, label_escaped,
+               label_width + value_width / 2 -1, value_escaped,
+               label_width + value_width / 2 -1, value_escaped);
+}
diff --git a/src/web_buffer_svg.h b/src/web_buffer_svg.h
new file mode 100644 (file)
index 0000000..ff3922e
--- /dev/null
@@ -0,0 +1,9 @@
+#include "web_buffer.h"
+#include "dictionary.h"
+
+#ifndef NETDATA_WEB_BUFFER_SVG_H
+#define NETDATA_WEB_BUFFER_SVG_H 1
+
+extern void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const char *units, const char *label_color, const char *value_color, int value_is_null);
+
+#endif /* NETDATA_WEB_BUFFER_SVG_H */
index 7d3c433d593345bfc36c0b977ed12a34f680c254..87787f785aaac23f8518302bd99f9210f0c9e06c 100644 (file)
@@ -30,7 +30,7 @@
 #include "rrd.h"
 #include "rrd2json.h"
 #include "registry.h"
-
+#include "web_buffer_svg.h"
 #include "web_client.h"
 
 #define INITIAL_WEB_DATA_LENGTH 16384
@@ -686,6 +686,133 @@ cleanup:
        return ret;
 }
 
+int web_client_api_v1_badge(struct web_client *w, char *url) {
+       // chart
+       // dimensions
+       // before
+       // after
+       // points
+
+       int ret = 400;
+       buffer_flush(w->response.data);
+
+       BUFFER *dimensions = NULL;
+       
+       const char *chart = NULL
+                       , *before_str = NULL
+                       , *after_str = NULL
+                       , *points_str = NULL
+                       , *multiply_str = NULL
+                       , *divide_str = NULL
+                       , *label = NULL
+                       , *units = NULL
+                       , *label_color = NULL
+                       , *value_color = NULL;
+
+       int group = GROUP_MAX;
+       uint32_t format = DATASOURCE_JSON;
+       uint32_t options = 0x00000000;
+
+       while(url) {
+               char *value = mystrsep(&url, "/?&[]");
+               if(!value || !*value) continue;
+
+               char *name = mystrsep(&value, "=");
+               if(!name || !*name) continue;
+               if(!value || !*value) continue;
+
+               debug(D_WEB_CLIENT, "%llu: API v1 badge.svg 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") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
+                       if(!dimensions) dimensions = buffer_create(strlen(value));
+                       if(dimensions) {
+                               buffer_strcat(dimensions, "|");
+                               buffer_strcat(dimensions, value);
+                       }
+               }
+               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 = 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 |= web_client_api_request_v1_data_options(value);
+               }
+               else if(!strcmp(name, "label")) label = value;
+               else if(!strcmp(name, "units")) units = value;
+               else if(!strcmp(name, "label_color")) label_color = value;
+               else if(!strcmp(name, "value_color")) value_color = value;
+               else if(!strcmp(name, "multiply")) multiply_str = value;
+               else if(!strcmp(name, "divide")) divide_str = value;
+       }
+
+       if(!chart || !*chart) {
+               buffer_sprintf(w->response.data, "No chart id is given at the request.");
+               goto cleanup;
+       }
+
+       RRDSET *st = rrdset_find(chart);
+       if(!st) st = rrdset_find_byname(chart);
+       if(!st) {
+               buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart);
+               ret = 404;
+               goto cleanup;
+       }
+
+       long long multiply = (multiply_str && *multiply_str)?atol(multiply_str):1;
+       long long divide   = (divide_str   && *divide_str  )?atol(divide_str):1;
+       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;
+
+       if(!label) {
+               if(dimensions) {
+                       const char *dim = buffer_tostring(dimensions);
+                       if(*dim == '|') dim++;
+                       label = dim;
+               }
+               else
+                       label = st->name;
+       }
+       if(!units) {
+               if(options & RRDR_OPTION_PERCENTAGE)
+                       units="%";
+               else
+                       units = st->units;
+       }
+
+       debug(D_WEB_CLIENT, "%llu: API command 'badge.svg' 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
+                       );
+
+       time_t latest_timestamp = 0;
+       int value_is_null = 1;
+       calculated_number n = 0;
+       ret = rrd2value(st, w->response.data, &n, dimensions, points, after, before, group, options, &latest_timestamp, &value_is_null);
+       buffer_svg(w->response.data, label, n * multiply / divide, units, label_color, value_color, value_is_null);
+       return ret;
+
+cleanup:
+       if(dimensions) buffer_free(dimensions);
+       return ret;
+}
+
 // returns the HTTP code
 int web_client_api_request_v1_data(struct web_client *w, char *url)
 {
@@ -1076,15 +1203,15 @@ int web_client_api_request_v1_registry(struct web_client *w, char *url)
        return 400;
 }
 
-int web_client_api_request_v1(struct web_client *w, char *url)
-{
-       static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0;
+int web_client_api_request_v1(struct web_client *w, char *url) {
+       static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0, hash_badge = 0;
 
        if(unlikely(hash_data == 0)) {
                hash_data = simple_hash("data");
                hash_chart = simple_hash("chart");
                hash_charts = simple_hash("charts");
                hash_registry = simple_hash("registry");
+               hash_badge = simple_hash("badge.svg");
        }
 
        // get the command
@@ -1105,6 +1232,9 @@ int web_client_api_request_v1(struct web_client *w, char *url)
                else if(hash == hash_registry && !strcmp(tok, "registry"))
                        return web_client_api_request_v1_registry(w, url);
 
+               else if(hash == hash_badge && !strcmp(tok, "badge.svg"))
+                       return web_client_api_v1_badge(w, url);
+
                else {
                        buffer_flush(w->response.data);
                        buffer_sprintf(w->response.data, "Unsupported v1 API command: %s", tok);
index df423cb47754d25adc35b28464b8b1a3ed13f161..a3198857672d0ba3f13b78544340953670915bbe 100644 (file)
@@ -14,6 +14,9 @@
 #include "web_buffer.h"
 #include "dictionary.h"
 
+#ifndef NETDATA_WEB_CLIENT_H
+#define NETDATA_WEB_CLIENT_H 1
+
 #define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60
 extern int web_client_timeout;
 
@@ -21,9 +24,6 @@ extern int web_client_timeout;
 extern int web_enable_gzip, web_gzip_level, web_gzip_strategy, web_donotrack_comply;
 #endif /* NETDATA_WITH_ZLIB */
 
-#ifndef NETDATA_WEB_CLIENT_H
-#define NETDATA_WEB_CLIENT_H 1
-
 #define WEB_CLIENT_MODE_NORMAL         0
 #define WEB_CLIENT_MODE_FILECOPY       1
 #define WEB_CLIENT_MODE_OPTIONS                2