From 9a68aa7f2de919beebfb43131c7301c91041f3b7 Mon Sep 17 00:00:00 2001 From: "Costa Tsaousis (ktsaou)" Date: Sat, 4 Jun 2016 15:58:03 +0300 Subject: [PATCH] added support for generating SVG badges with chart data --- .gitignore | 1 + src/Makefile.am | 1 + src/rrd2json.c | 189 ++++++++------ src/rrd2json.h | 2 +- src/web_buffer_svg.c | 569 +++++++++++++++++++++++++++++++++++++++++++ src/web_buffer_svg.h | 9 + src/web_client.c | 138 ++++++++++- src/web_client.h | 6 +- 8 files changed, 838 insertions(+), 77 deletions(-) create mode 100644 src/web_buffer_svg.c create mode 100644 src/web_buffer_svg.h diff --git a/.gitignore b/.gitignore index 02801b77..a4c1644f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index e9fc8f33..ac7ac44f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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) diff --git a/src/rrd2json.c b/src/rrd2json.c index e0bd0667..5adaab05 100644 --- a/src/rrd2json.c +++ b/src/rrd2json.c @@ -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); diff --git a/src/rrd2json.h b/src/rrd2json.h index d1f850d4..6ba9fb7f 100644 --- a/src/rrd2json.h +++ b/src/rrd2json.h @@ -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 index 00000000..3c342e0c --- /dev/null +++ b/src/web_buffer_svg.c @@ -0,0 +1,569 @@ +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include + +#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, "&"); + i -= 5; + dst += 5; + src++; + } + else goto cleanup; + break; + + case '<': + if(i > 4) { + strcpy(dst, "<"); + i -= 4; + dst += 4; + src++; + } + else goto cleanup; + break; + + case '>': + if(i > 4) { + strcpy(dst, ">"); + i -= 4; + dst += 4; + src++; + } + else goto cleanup; + break; + + case '"': + if(i > 6) { + strcpy(dst, """); + i -= 6; + dst += 6; + src++; + } + else goto cleanup; + break; + + case '\'': + if(i > 6) { + strcpy(dst, "'"); + 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: + // colormin|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, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "%s" + "%s" + "%s" + "%s" + "" + "", + 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 index 00000000..ff3922ee --- /dev/null +++ b/src/web_buffer_svg.h @@ -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 */ diff --git a/src/web_client.c b/src/web_client.c index 7d3c433d..87787f78 100644 --- a/src/web_client.c +++ b/src/web_client.c @@ -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); diff --git a/src/web_client.h b/src/web_client.h index df423cb4..a3198857 100644 --- a/src/web_client.h +++ b/src/web_client.h @@ -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 -- 2.39.2