@cryptotaxi247 / netdata-1 / commits / 9a68aa7f2

added support for generating SVG badges with chart data

Costa Tsaousis (ktsaou) committed Jun 4, 2016 at 15:58 UTC 9a68aa7f2de919beebfb43131c7301c91041f3b7
8 files changed +838 -77
.gitignore
+1
@@ -58,6 +58,7 @@ web/gadget.xml
58 web/index_new.html
59 web/version.txt
60
61 +system/netdata-lsb
62 system/netdata-openrc
63 system/netdata-init-d
64 system/netdata.logrotate
src/Makefile.am
+1
@@ -67,6 +67,7 @@ netdata_SOURCES = \
67 unit_test.c unit_test.h \
68 url.c url.h \
69 web_buffer.c web_buffer.h \
70 + web_buffer_svg.c web_buffer_svg.h \
71 web_client.c web_client.h \
72 web_server.c web_server.h \
73 $(NULL)
src/rrd2json.c
+120 -69
@@ -985,104 +985,121 @@ static void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t options, const char *startlin
985 //info("RRD2CSV(): %s: END", r->st->id);
986 }
987
988 -static void rrdr2ssv(RRDR *r, BUFFER *wb, uint32_t options, const char *prefix, const char *separator, const char *suffix)
989 -{
990 - //info("RRD2SSV(): %s: BEGIN", r->st->id);
991 - long c, i;
988 +inline static calculated_number rrdr2value(RRDR *r, long i, uint32_t options, int *all_values_are_null) {
989 + long c;
990 RRDDIM *d;
991
994 - buffer_strcat(wb, prefix);
995 - long start = 0, end = rrdr_rows(r), step = 1;
996 - if((options & RRDR_OPTION_REVERSED)) {
997 - start = rrdr_rows(r) - 1;
998 - end = -1;
999 - step = -1;
1000 - }
992 + calculated_number *cn = &r->v[ i * r->d ];
993 + uint8_t *co = &r->o[ i * r->d ];
994 +
995 + calculated_number sum = 0, min = 0, max = 0, v;
996 + int all_null = 1, init = 1;
997
1002 - // for each line in the array
998 calculated_number total = 1;
1004 - for(i = start; i != end ;i += step) {
999 + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) {
1000 + total = 0;
1001 + for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) {
1002 + calculated_number n = cn[c];
1003
1006 - calculated_number *cn = &r->v[ i * r->d ];
1007 - uint8_t *co = &r->o[ i * r->d ];
1004 + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
1005 + n = -n;
1006
1009 - calculated_number sum = 0, min = 0, max = 0, v;
1010 - int all_null = 1, init = 1;
1007 + total += n;
1008 + }
1009 + // prevent a division by zero
1010 + if(total == 0) total = 1;
1011 + }
1012
1012 - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) {
1013 - total = 0;
1014 - for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) {
1015 - calculated_number n = cn[c];
1013 + // for each dimension
1014 + for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) {
1015 + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue;
1016 + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue;
1017
1017 - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
1018 - n = -n;
1018 + calculated_number n = cn[c];
1019
1020 - total += n;
1020 + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
1021 + n = -n;
1022 +
1023 + if(unlikely(options & RRDR_OPTION_PERCENTAGE))
1024 + n = n * 100 / total;
1025 +
1026 + if(unlikely(init)) {
1027 + if(n > 0) {
1028 + min = 0;
1029 + max = n;
1030 }
1022 - // prevent a division by zero
1023 - if(total == 0) total = 1;
1031 + else {
1032 + min = n;
1033 + max = 0;
1034 + }
1035 + init = 0;
1036 }
1037
1026 - // for each dimension
1027 - for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) {
1028 - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue;
1029 - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue;
1038 + if(likely(!(co[c] & RRDR_EMPTY))) {
1039 + all_null = 0;
1040 + sum += n;
1041 + }
1042
1031 - calculated_number n = cn[c];
1043 + if(n < min) min = n;
1044 + if(n > max) max = n;
1045 + }
1046
1033 - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
1034 - n = -n;
1047 + if(unlikely(all_null)) {
1048 + if(likely(*all_values_are_null))
1049 + *all_values_are_null = 1;
1050 + return 0;
1051 + }
1052 + else {
1053 + if(likely(*all_values_are_null))
1054 + *all_values_are_null = 0;
1055 + }
1056
1036 - if(unlikely(options & RRDR_OPTION_PERCENTAGE))
1037 - n = n * 100 / total;
1057 + if(options & RRDR_OPTION_MIN2MAX)
1058 + v = max - min;
1059 + else
1060 + v = sum;
1061
1039 - if(unlikely(init)) {
1040 - if(n > 0) {
1041 - min = 0;
1042 - max = n;
1043 - }
1044 - else {
1045 - min = n;
1046 - max = 0;
1047 - }
1048 - init = 0;
1049 - }
1062 + return v;
1063 +}
1064
1051 - if(likely(!(co[c] & RRDR_EMPTY))) {
1052 - all_null = 0;
1053 - sum += n;
1054 - }
1065 +static void rrdr2ssv(RRDR *r, BUFFER *wb, uint32_t options, const char *prefix, const char *separator, const char *suffix)
1066 +{
1067 + //info("RRD2SSV(): %s: BEGIN", r->st->id);
1068 + long i;
1069
1056 - if(n < min) min = n;
1057 - if(n > max) max = n;
1070 + buffer_strcat(wb, prefix);
1071 + long start = 0, end = rrdr_rows(r), step = 1;
1072 + if((options & RRDR_OPTION_REVERSED)) {
1073 + start = rrdr_rows(r) - 1;
1074 + end = -1;
1075 + step = -1;
1076 + }
1077 +
1078 + // for each line in the array
1079 + for(i = start; i != end ;i += step) {
1080 + int all_values_are_null = 0;
1081 + calculated_number v = rrdr2value(r, i, options, &all_values_are_null);
1082 +
1083 + if(likely(i != start)) {
1084 + if(r->min > v) r->min = v;
1085 + if(r->max < v) r->max = v;
1086 + }
1087 + else {
1088 + r->min = v;
1089 + r->max = v;
1090 }
1091
1092 if(likely(i != start))
1093 buffer_strcat(wb, separator);
1094
1063 - if(all_null) {
1095 + if(all_values_are_null) {
1096 if(options & RRDR_OPTION_NULL2ZERO)
1097 buffer_strcat(wb, "0");
1098 else
1099 buffer_strcat(wb, "null");
1100 }
1069 - else {
1070 - if(options & RRDR_OPTION_MIN2MAX)
1071 - v = max - min;
1072 - else
1073 - v = sum;
1074 -
1075 - if(likely(i != start)) {
1076 - if(r->min > v) r->min = v;
1077 - if(r->max < v) r->max = v;
1078 - }
1079 - else {
1080 - r->min = v;
1081 - r->max = v;
1082 - }
1083 -
1101 + else
1102 buffer_rrd_value(wb, v);
1085 - }
1103 }
1104 buffer_strcat(wb, suffix);
1105 //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
1530 return r;
1531 }
1532
1533 +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)
1534 +{
1535 + RRDR *r = rrd2rrdr(st, points, after, before, group_method);
1536 + if(!r) {
1537 + if(value_is_null) *value_is_null = 1;
1538 + return 500;
1539 + }
1540 +
1541 + if(rrdr_rows(r) == 0) {
1542 + rrdr_free(r);
1543 + if(value_is_null) *value_is_null = 1;
1544 + return 400;
1545 + }
1546 +
1547 + if(r->result_options & RRDR_RESULT_OPTION_RELATIVE)
1548 + wb->options |= WB_CONTENT_NO_CACHEABLE;
1549 + else if(r->result_options & RRDR_RESULT_OPTION_ABSOLUTE)
1550 + wb->options |= WB_CONTENT_CACHEABLE;
1551 +
1552 + options = rrdr_check_options(r, options, (dimensions)?buffer_tostring(dimensions):NULL);
1553 +
1554 + if(dimensions)
1555 + rrdr_disable_not_selected_dimensions(r, buffer_tostring(dimensions));
1556 +
1557 + if(latest_timestamp)
1558 + *latest_timestamp = r->before;
1559 +
1560 + long i = (options & RRDR_OPTION_REVERSED)?rrdr_rows(r) - 1:0;
1561 + *n = rrdr2value(r, i, options, value_is_null);
1562 +
1563 + rrdr_free(r);
1564 + return 200;
1565 +}
1566 +
1567 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)
1568 {
1569 RRDR *r = rrd2rrdr(st, points, after, before, group_method);
src/rrd2json.h
+1 -1
@@ -64,6 +64,6 @@ extern void rrd_stats_all_json(BUFFER *wb);
64 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);
65
66 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);
67 -
67 +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);
68
69 #endif /* NETDATA_RRD2JSON_H */
src/web_buffer_svg.c new
+569
@@ -0,0 +1,569 @@
1 +#ifdef HAVE_CONFIG_H
2 +#include <config.h>
3 +#endif
4 +#include <math.h>
5 +#include <string.h>
6 +#include <ctype.h>
7 +#include <stdlib.h>
8 +
9 +#include "common.h"
10 +#include "log.h"
11 +#include "appconfig.h"
12 +#include "url.h"
13 +#include "web_buffer.h"
14 +#include "web_buffer_svg.h"
15 +
16 +#define BADGE_HORIZONTAL_PADDING 4
17 +#define VERDANA_KERNING 0.5
18 +
19 +/*
20 + * verdana11_widths[] has been generated with this method:
21 + * https://github.com/badges/shields/blob/master/measure-text.js
22 +*/
23 +
24 +double verdana11_widths[256] = {
25 + [0] = 0.0,
26 + [1] = 0.0,
27 + [2] = 0.0,
28 + [3] = 0.0,
29 + [4] = 0.0,
30 + [5] = 0.0,
31 + [6] = 0.0,
32 + [7] = 0.0,
33 + [8] = 0.0,
34 + [9] = 0.0,
35 + [10] = 0.0,
36 + [11] = 0.0,
37 + [12] = 0.0,
38 + [13] = 0.0,
39 + [14] = 0.0,
40 + [15] = 0.0,
41 + [16] = 0.0,
42 + [17] = 0.0,
43 + [18] = 0.0,
44 + [19] = 0.0,
45 + [20] = 0.0,
46 + [21] = 0.0,
47 + [22] = 0.0,
48 + [23] = 0.0,
49 + [24] = 0.0,
50 + [25] = 0.0,
51 + [26] = 0.0,
52 + [27] = 0.0,
53 + [28] = 0.0,
54 + [29] = 0.0,
55 + [30] = 0.0,
56 + [31] = 0.0,
57 + [32] = 3.8671874999999996, //
58 + [33] = 4.3291015625, // !
59 + [34] = 5.048828125, // "
60 + [35] = 9.001953125, // #
61 + [36] = 6.9931640625, // $
62 + [37] = 11.837890625, // %
63 + [38] = 7.992187499999999, // &
64 + [39] = 2.9541015625, // '
65 + [40] = 4.9951171875, // (
66 + [41] = 4.9951171875, // )
67 + [42] = 6.9931640625, // *
68 + [43] = 9.001953125, // +
69 + [44] = 4.00146484375, // ,
70 + [45] = 4.9951171875, // -
71 + [46] = 4.00146484375, // .
72 + [47] = 4.9951171875, // /
73 + [48] = 6.9931640625, // 0
74 + [49] = 6.9931640625, // 1
75 + [50] = 6.9931640625, // 2
76 + [51] = 6.9931640625, // 3
77 + [52] = 6.9931640625, // 4
78 + [53] = 6.9931640625, // 5
79 + [54] = 6.9931640625, // 6
80 + [55] = 6.9931640625, // 7
81 + [56] = 6.9931640625, // 8
82 + [57] = 6.9931640625, // 9
83 + [58] = 4.9951171875, // :
84 + [59] = 4.9951171875, // ;
85 + [60] = 9.001953125, // <
86 + [61] = 9.001953125, // =
87 + [62] = 9.001953125, // >
88 + [63] = 5.99951171875, // ?
89 + [64] = 11.0, // @
90 + [65] = 7.51953125, // A
91 + [66] = 7.541015625, // B
92 + [67] = 7.680664062499999, // C
93 + [68] = 8.4755859375, // D
94 + [69] = 6.95556640625, // E
95 + [70] = 6.32177734375, // F
96 + [71] = 8.529296875, // G
97 + [72] = 8.26611328125, // H
98 + [73] = 4.6298828125, // I
99 + [74] = 5.00048828125, // J
100 + [75] = 7.62158203125, // K
101 + [76] = 6.123046875, // L
102 + [77] = 9.2705078125, // M
103 + [78] = 8.228515625, // N
104 + [79] = 8.658203125, // O
105 + [80] = 6.63330078125, // P
106 + [81] = 8.658203125, // Q
107 + [82] = 7.6484375, // R
108 + [83] = 7.51953125, // S
109 + [84] = 6.7783203125, // T
110 + [85] = 8.05126953125, // U
111 + [86] = 7.51953125, // V
112 + [87] = 10.87646484375, // W
113 + [88] = 7.53564453125, // X
114 + [89] = 6.767578125, // Y
115 + [90] = 7.53564453125, // Z
116 + [91] = 4.9951171875, // [
117 + [92] = 4.9951171875, // backslash
118 + [93] = 4.9951171875, // ]
119 + [94] = 9.001953125, // ^
120 + [95] = 6.9931640625, // _
121 + [96] = 6.9931640625, // `
122 + [97] = 6.6064453125, // a
123 + [98] = 6.853515625, // b
124 + [99] = 5.73095703125, // c
125 + [100] = 6.853515625, // d
126 + [101] = 6.552734375, // e
127 + [102] = 3.8671874999999996, // f
128 + [103] = 6.853515625, // g
129 + [104] = 6.9609375, // h
130 + [105] = 3.0185546875, // i
131 + [106] = 3.78662109375, // j
132 + [107] = 6.509765625, // k
133 + [108] = 3.0185546875, // l
134 + [109] = 10.69921875, // m
135 + [110] = 6.9609375, // n
136 + [111] = 6.67626953125, // o
137 + [112] = 6.853515625, // p
138 + [113] = 6.853515625, // q
139 + [114] = 4.6943359375, // r
140 + [115] = 5.73095703125, // s
141 + [116] = 4.33447265625, // t
142 + [117] = 6.9609375, // u
143 + [118] = 6.509765625, // v
144 + [119] = 9.001953125, // w
145 + [120] = 6.509765625, // x
146 + [121] = 6.509765625, // y
147 + [122] = 5.779296875, // z
148 + [123] = 6.982421875, // {
149 + [124] = 4.9951171875, // |
150 + [125] = 6.982421875, // }
151 + [126] = 9.001953125, // ~
152 + [127] = 0.0,
153 + [128] = 0.0,
154 + [129] = 0.0,
155 + [130] = 0.0,
156 + [131] = 0.0,
157 + [132] = 0.0,
158 + [133] = 0.0,
159 + [134] = 0.0,
160 + [135] = 0.0,
161 + [136] = 0.0,
162 + [137] = 0.0,
163 + [138] = 0.0,
164 + [139] = 0.0,
165 + [140] = 0.0,
166 + [141] = 0.0,
167 + [142] = 0.0,
168 + [143] = 0.0,
169 + [144] = 0.0,
170 + [145] = 0.0,
171 + [146] = 0.0,
172 + [147] = 0.0,
173 + [148] = 0.0,
174 + [149] = 0.0,
175 + [150] = 0.0,
176 + [151] = 0.0,
177 + [152] = 0.0,
178 + [153] = 0.0,
179 + [154] = 0.0,
180 + [155] = 0.0,
181 + [156] = 0.0,
182 + [157] = 0.0,
183 + [158] = 0.0,
184 + [159] = 0.0,
185 + [160] = 0.0,
186 + [161] = 0.0,
187 + [162] = 0.0,
188 + [163] = 0.0,
189 + [164] = 0.0,
190 + [165] = 0.0,
191 + [166] = 0.0,
192 + [167] = 0.0,
193 + [168] = 0.0,
194 + [169] = 0.0,
195 + [170] = 0.0,
196 + [171] = 0.0,
197 + [172] = 0.0,
198 + [173] = 0.0,
199 + [174] = 0.0,
200 + [175] = 0.0,
201 + [176] = 0.0,
202 + [177] = 0.0,
203 + [178] = 0.0,
204 + [179] = 0.0,
205 + [180] = 0.0,
206 + [181] = 0.0,
207 + [182] = 0.0,
208 + [183] = 0.0,
209 + [184] = 0.0,
210 + [185] = 0.0,
211 + [186] = 0.0,
212 + [187] = 0.0,
213 + [188] = 0.0,
214 + [189] = 0.0,
215 + [190] = 0.0,
216 + [191] = 0.0,
217 + [192] = 0.0,
218 + [193] = 0.0,
219 + [194] = 0.0,
220 + [195] = 0.0,
221 + [196] = 0.0,
222 + [197] = 0.0,
223 + [198] = 0.0,
224 + [199] = 0.0,
225 + [200] = 0.0,
226 + [201] = 0.0,
227 + [202] = 0.0,
228 + [203] = 0.0,
229 + [204] = 0.0,
230 + [205] = 0.0,
231 + [206] = 0.0,
232 + [207] = 0.0,
233 + [208] = 0.0,
234 + [209] = 0.0,
235 + [210] = 0.0,
236 + [211] = 0.0,
237 + [212] = 0.0,
238 + [213] = 0.0,
239 + [214] = 0.0,
240 + [215] = 0.0,
241 + [216] = 0.0,
242 + [217] = 0.0,
243 + [218] = 0.0,
244 + [219] = 0.0,
245 + [220] = 0.0,
246 + [221] = 0.0,
247 + [222] = 0.0,
248 + [223] = 0.0,
249 + [224] = 0.0,
250 + [225] = 0.0,
251 + [226] = 0.0,
252 + [227] = 0.0,
253 + [228] = 0.0,
254 + [229] = 0.0,
255 + [230] = 0.0,
256 + [231] = 0.0,
257 + [232] = 0.0,
258 + [233] = 0.0,
259 + [234] = 0.0,
260 + [235] = 0.0,
261 + [236] = 0.0,
262 + [237] = 0.0,
263 + [238] = 0.0,
264 + [239] = 0.0,
265 + [240] = 0.0,
266 + [241] = 0.0,
267 + [242] = 0.0,
268 + [243] = 0.0,
269 + [244] = 0.0,
270 + [245] = 0.0,
271 + [246] = 0.0,
272 + [247] = 0.0,
273 + [248] = 0.0,
274 + [249] = 0.0,
275 + [250] = 0.0,
276 + [251] = 0.0,
277 + [252] = 0.0,
278 + [253] = 0.0,
279 + [254] = 0.0,
280 + [255] = 0.0
281 +};
282 +
283 +// find the width of the string using the verdana 11points font
284 +// re-write the string in place, skiping zero-length characters
285 +static inline int verdana11_width(char *s) {
286 + double w = 0.0;
287 + char *d = s;
288 +
289 + while(*s) {
290 + double t = verdana11_widths[(unsigned char)*s];
291 + if(t == 0.0)
292 + s++;
293 + else {
294 + w += t + VERDANA_KERNING;
295 + if(d != s)
296 + *d++ = *s++;
297 + else
298 + d = ++s;
299 + }
300 + }
301 +
302 + *d = '\0';
303 + w -= VERDANA_KERNING;
304 + return ceil(w);
305 +}
306 +
307 +static inline size_t escape_xmlz(char *dst, const char *src, size_t len) {
308 + size_t i = len;
309 +
310 + // required escapes from
311 + // https://github.com/badges/shields/blob/master/badge.js
312 + while(*src && i) {
313 + switch(*src) {
314 + case '&':
315 + if(i > 5) {
316 + strcpy(dst, "&amp;");
317 + i -= 5;
318 + dst += 5;
319 + src++;
320 + }
321 + else goto cleanup;
322 + break;
323 +
324 + case '<':
325 + if(i > 4) {
326 + strcpy(dst, "&lt;");
327 + i -= 4;
328 + dst += 4;
329 + src++;
330 + }
331 + else goto cleanup;
332 + break;
333 +
334 + case '>':
335 + if(i > 4) {
336 + strcpy(dst, "&gt;");
337 + i -= 4;
338 + dst += 4;
339 + src++;
340 + }
341 + else goto cleanup;
342 + break;
343 +
344 + case '"':
345 + if(i > 6) {
346 + strcpy(dst, "&quot;");
347 + i -= 6;
348 + dst += 6;
349 + src++;
350 + }
351 + else goto cleanup;
352 + break;
353 +
354 + case '\'':
355 + if(i > 6) {
356 + strcpy(dst, "&apos;");
357 + i -= 6;
358 + dst += 6;
359 + src++;
360 + }
361 + else goto cleanup;
362 + break;
363 +
364 + default:
365 + i--;
366 + *dst++ = *src++;
367 + break;
368 + }
369 + }
370 +
371 +cleanup:
372 + *dst = '\0';
373 + return len - i;
374 +}
375 +
376 +static inline const char *fix_units(const char *units) {
377 + if(!strcmp(units, "percentage") || !strcmp(units, "percent") || !strcmp(units, "pcent")) return "%";
378 + return units;
379 +}
380 +
381 +static inline const char *color_map(const char *color) {
382 + // colors from:
383 + // https://github.com/badges/shields/blob/master/colorscheme.json
384 + if(!strcmp(color, "brightgreen")) return "#4c1";
385 + else if(!strcmp(color, "green")) return "#97CA00";
386 + else if(!strcmp(color, "yellow")) return "#dfb317";
387 + else if(!strcmp(color, "yellowgreen")) return "#a4a61d";
388 + else if(!strcmp(color, "orange")) return "#fe7d37";
389 + else if(!strcmp(color, "red")) return "#e05d44";
390 + else if(!strcmp(color, "blue")) return "#007ec6";
391 + else if(!strcmp(color, "grey")) return "#555";
392 + else if(!strcmp(color, "gray")) return "#555";
393 + else if(!strcmp(color, "lightgrey")) return "#9f9f9f";
394 + else if(!strcmp(color, "lightgray")) return "#9f9f9f";
395 + return color;
396 +}
397 +
398 +static inline void calc_colorz(const char *color, char *final, size_t len, calculated_number value, int value_is_null) {
399 + char color_buffer[256 + 1] = "";
400 + char value_buffer[256 + 1] = "";
401 + char comparison = '>';
402 +
403 + // example input:
404 + // color<max|color>min|color:null...
405 +
406 + const char *c = color;
407 + while(*c) {
408 + char *dc = color_buffer, *dv = NULL;
409 + size_t ci = 0, vi = 0;
410 +
411 + const char *t = c;
412 +
413 + while(*t && *t != '|') {
414 + switch(*t) {
415 + case ':':
416 + comparison = '=';
417 + dv = value_buffer;
418 + break;
419 +
420 + case '}':
421 + case ')':
422 + case '>':
423 + if(t[1] == '=') {
424 + comparison = ')';
425 + t++;
426 + }
427 + else
428 + comparison = '>';
429 + dv = value_buffer;
430 + break;
431 +
432 + case '{':
433 + case '(':
434 + case '<':
435 + if(t[1] == '=') {
436 + comparison = '(';
437 + t++;
438 + }
439 + else
440 + comparison = '<';
441 + dv = value_buffer;
442 + break;
443 +
444 + default:
445 + if(dv) {
446 + if(vi < 256) {
447 + vi++;
448 + *dv++ = *t;
449 + }
450 + }
451 + else {
452 + if(ci < 256) {
453 + ci++;
454 + *dc++ = *t;
455 + }
456 + }
457 + break;
458 + }
459 +
460 + t++;
461 + }
462 +
463 + // prepare for next iteration
464 + if(*t == '|') t++;
465 + c = t;
466 +
467 + // do the math
468 + *dc = '\0';
469 + if(dv) {
470 + *dv = '\0';
471 +
472 + if(value_is_null) {
473 + if(!*value_buffer || !strcmp(value_buffer, "null"))
474 + break;
475 + }
476 + else {
477 + calculated_number v = strtold(value_buffer, NULL);
478 +
479 + if(comparison == '<' && value < v) break;
480 + else if(comparison == '(' && value <= v) break;
481 + else if(comparison == '>' && value > v) break;
482 + else if(comparison == ')' && value >= v) break;
483 + else if(comparison == '=' && value == v) break;
484 + }
485 + }
486 + else
487 + break;
488 + }
489 +
490 + const char *b;
491 + if(color_buffer[0])
492 + b = color_buffer;
493 + else
494 + b = color;
495 +
496 + strncpyz(final, b, len);
497 +}
498 +
499 +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) {
500 + char label_buffer[256 + 1], value_string[512 + 1], value_color_buffer[256 + 1];
501 + char label_escaped[256 + 1], value_escaped[512 + 1], label_color_escaped[256 + 1], value_color_escaped[256 + 1];
502 + int label_width, value_width, total_width;
503 +
504 + if(!label_color || !*label_color) label_color = "#555";
505 + if(!value_color || !*value_color) value_color = "#4c1";
506 +
507 + units = fix_units(units);
508 + calc_colorz(value_color, value_color_buffer, 256, value, value_is_null);
509 +
510 + char *separator = "";
511 + if(isalnum(*units)) separator = " ";
512 +
513 + if(value_is_null)
514 + strcpy(value_string, "-");
515 + else {
516 + calculated_number abs = (value < (calculated_number)0)?-value:value;
517 + if(abs > (calculated_number)1000.0) snprintfz(value_string, 512, "%0.0Lf%s%s", (long double)value, separator, units);
518 + else if(abs > (calculated_number)100.0) snprintfz(value_string, 512, "%0.1Lf%s%s", (long double)value, separator, units);
519 + else if(abs > (calculated_number)1.0) snprintfz(value_string, 512, "%0.2Lf%s%s", (long double)value, separator, units);
520 + else if(abs > (calculated_number)0.1) snprintfz(value_string, 512, "%0.3Lf%s%s", (long double)value, separator, units);
521 + else snprintfz(value_string, 512, "%0.4Lf%s%s", (long double)value, separator, units);
522 + }
523 +
524 + // we need to copy the label, since verdana11_width may write to it
525 + strncpyz(label_buffer, label, 256);
526 +
527 + label_width = verdana11_width(label_buffer) + (BADGE_HORIZONTAL_PADDING * 2);
528 + value_width = verdana11_width(value_string) + (BADGE_HORIZONTAL_PADDING * 2);
529 + total_width = label_width + value_width;
530 +
531 + escape_xmlz(label_escaped, label_buffer, 256);
532 + escape_xmlz(value_escaped, value_string, 256);
533 + escape_xmlz(label_color_escaped, color_map(label_color), 256);
534 + escape_xmlz(value_color_escaped, color_map(value_color_buffer), 256);
535 +
536 + wb->contenttype = CT_IMAGE_SVG_XML;
537 +
538 + // svg template from:
539 + // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
540 + buffer_sprintf(wb,
541 + "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%zu\" height=\"20\">"
542 + "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">"
543 + "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>"
544 + "<stop offset=\"1\" stop-opacity=\".1\"/>"
545 + "</linearGradient>"
546 + "<mask id=\"round\">"
547 + "<rect width=\"%zu\" height=\"20\" rx=\"3\" fill=\"#fff\"/>"
548 + "</mask>"
549 + "<g mask=\"url(#round)\">"
550 + "<rect width=\"%zu\" height=\"20\" fill=\"%s\"/>"
551 + "<rect x=\"%zu\" width=\"%zu\" height=\"20\" fill=\"%s\"/>"
552 + "<rect width=\"%zu\" height=\"20\" fill=\"url(#smooth)\"/>"
553 + "</g>"
554 + "<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">"
555 + "<text x=\"%zu\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
556 + "<text x=\"%zu\" y=\"14\">%s</text>"
557 + "<text x=\"%zu\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
558 + "<text x=\"%zu\" y=\"14\">%s</text>"
559 + "</g>"
560 + "</svg>",
561 + total_width, total_width,
562 + label_width, label_color_escaped,
563 + label_width, value_width, value_color_escaped,
564 + total_width,
565 + label_width / 2, label_escaped,
566 + label_width / 2, label_escaped,
567 + label_width + value_width / 2 -1, value_escaped,
568 + label_width + value_width / 2 -1, value_escaped);
569 +}
src/web_buffer_svg.h new
+9
@@ -0,0 +1,9 @@
1 +#include "web_buffer.h"
2 +#include "dictionary.h"
3 +
4 +#ifndef NETDATA_WEB_BUFFER_SVG_H
5 +#define NETDATA_WEB_BUFFER_SVG_H 1
6 +
7 +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);
8 +
9 +#endif /* NETDATA_WEB_BUFFER_SVG_H */
src/web_client.c
+134 -4
@@ -30,7 +30,7 @@
30 #include "rrd.h"
31 #include "rrd2json.h"
32 #include "registry.h"
33 -
33 +#include "web_buffer_svg.h"
34 #include "web_client.h"
35
36 #define INITIAL_WEB_DATA_LENGTH 16384
@@ -686,6 +686,133 @@ cleanup:
686 return ret;
687 }
688
689 +int web_client_api_v1_badge(struct web_client *w, char *url) {
690 + // chart
691 + // dimensions
692 + // before
693 + // after
694 + // points
695 +
696 + int ret = 400;
697 + buffer_flush(w->response.data);
698 +
699 + BUFFER *dimensions = NULL;
700 +
701 + const char *chart = NULL
702 + , *before_str = NULL
703 + , *after_str = NULL
704 + , *points_str = NULL
705 + , *multiply_str = NULL
706 + , *divide_str = NULL
707 + , *label = NULL
708 + , *units = NULL
709 + , *label_color = NULL
710 + , *value_color = NULL;
711 +
712 + int group = GROUP_MAX;
713 + uint32_t format = DATASOURCE_JSON;
714 + uint32_t options = 0x00000000;
715 +
716 + while(url) {
717 + char *value = mystrsep(&url, "/?&[]");
718 + if(!value || !*value) continue;
719 +
720 + char *name = mystrsep(&value, "=");
721 + if(!name || !*name) continue;
722 + if(!value || !*value) continue;
723 +
724 + debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value);
725 +
726 + // name and value are now the parameters
727 + // they are not null and not empty
728 +
729 + if(!strcmp(name, "chart")) chart = value;
730 + else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
731 + if(!dimensions) dimensions = buffer_create(strlen(value));
732 + if(dimensions) {
733 + buffer_strcat(dimensions, "|");
734 + buffer_strcat(dimensions, value);
735 + }
736 + }
737 + else if(!strcmp(name, "after")) after_str = value;
738 + else if(!strcmp(name, "before")) before_str = value;
739 + else if(!strcmp(name, "points")) points_str = value;
740 + else if(!strcmp(name, "group")) {
741 + group = web_client_api_request_v1_data_group(value);
742 + }
743 + else if(!strcmp(name, "format")) {
744 + format = web_client_api_request_v1_data_format(value);
745 + }
746 + else if(!strcmp(name, "options")) {
747 + options |= web_client_api_request_v1_data_options(value);
748 + }
749 + else if(!strcmp(name, "label")) label = value;
750 + else if(!strcmp(name, "units")) units = value;
751 + else if(!strcmp(name, "label_color")) label_color = value;
752 + else if(!strcmp(name, "value_color")) value_color = value;
753 + else if(!strcmp(name, "multiply")) multiply_str = value;
754 + else if(!strcmp(name, "divide")) divide_str = value;
755 + }
756 +
757 + if(!chart || !*chart) {
758 + buffer_sprintf(w->response.data, "No chart id is given at the request.");
759 + goto cleanup;
760 + }
761 +
762 + RRDSET *st = rrdset_find(chart);
763 + if(!st) st = rrdset_find_byname(chart);
764 + if(!st) {
765 + buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart);
766 + ret = 404;
767 + goto cleanup;
768 + }
769 +
770 + long long multiply = (multiply_str && *multiply_str)?atol(multiply_str):1;
771 + long long divide = (divide_str && *divide_str )?atol(divide_str):1;
772 + long long before = (before_str && *before_str )?atol(before_str):0;
773 + long long after = (after_str && *after_str )?atol(after_str):0;
774 + int points = (points_str && *points_str )?atoi(points_str):0;
775 +
776 + if(!label) {
777 + if(dimensions) {
778 + const char *dim = buffer_tostring(dimensions);
779 + if(*dim == '|') dim++;
780 + label = dim;
781 + }
782 + else
783 + label = st->name;
784 + }
785 + if(!units) {
786 + if(options & RRDR_OPTION_PERCENTAGE)
787 + units="%";
788 + else
789 + units = st->units;
790 + }
791 +
792 + 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'"
793 + , w->id
794 + , chart
795 + , (dimensions)?buffer_tostring(dimensions):""
796 + , after
797 + , before
798 + , points
799 + , group
800 + , format
801 + , options
802 + );
803 +
804 + time_t latest_timestamp = 0;
805 + int value_is_null = 1;
806 + calculated_number n = 0;
807 + ret = rrd2value(st, w->response.data, &n, dimensions, points, after, before, group, options, &latest_timestamp, &value_is_null);
808 + buffer_svg(w->response.data, label, n * multiply / divide, units, label_color, value_color, value_is_null);
809 + return ret;
810 +
811 +cleanup:
812 + if(dimensions) buffer_free(dimensions);
813 + return ret;
814 +}
815 +
816 // returns the HTTP code
817 int web_client_api_request_v1_data(struct web_client *w, char *url)
818 {
@@ -1076,15 +1203,15 @@ int web_client_api_request_v1_registry(struct web_client *w, char *url)
1203 return 400;
1204 }
1205
1079 -int web_client_api_request_v1(struct web_client *w, char *url)
1080 -{
1081 - static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0;
1206 +int web_client_api_request_v1(struct web_client *w, char *url) {
1207 + static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0, hash_badge = 0;
1208
1209 if(unlikely(hash_data == 0)) {
1210 hash_data = simple_hash("data");
1211 hash_chart = simple_hash("chart");
1212 hash_charts = simple_hash("charts");
1213 hash_registry = simple_hash("registry");
1214 + hash_badge = simple_hash("badge.svg");
1215 }
1216
1217 // get the command
@@ -1105,6 +1232,9 @@ int web_client_api_request_v1(struct web_client *w, char *url)
1232 else if(hash == hash_registry && !strcmp(tok, "registry"))
1233 return web_client_api_request_v1_registry(w, url);
1234
1235 + else if(hash == hash_badge && !strcmp(tok, "badge.svg"))
1236 + return web_client_api_v1_badge(w, url);
1237 +
1238 else {
1239 buffer_flush(w->response.data);
1240 buffer_sprintf(w->response.data, "Unsupported v1 API command: %s", tok);
src/web_client.h
+3 -3
@@ -14,6 +14,9 @@
14 #include "web_buffer.h"
15 #include "dictionary.h"
16
17 +#ifndef NETDATA_WEB_CLIENT_H
18 +#define NETDATA_WEB_CLIENT_H 1
19 +
20 #define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60
21 extern int web_client_timeout;
22
@@ -21,9 +24,6 @@ extern int web_client_timeout;
24 extern int web_enable_gzip, web_gzip_level, web_gzip_strategy, web_donotrack_comply;
25 #endif /* NETDATA_WITH_ZLIB */
26
24 -#ifndef NETDATA_WEB_CLIENT_H
25 -#define NETDATA_WEB_CLIENT_H 1
26 -
27 #define WEB_CLIENT_MODE_NORMAL 0
28 #define WEB_CLIENT_MODE_FILECOPY 1
29 #define WEB_CLIENT_MODE_OPTIONS 2