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, "&");
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, "<");
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, ">");
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, """);
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, "'");
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