web_buffer_svg.c 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "web_buffer_svg.h"
  3. #define BADGE_HORIZONTAL_PADDING 4
  4. #define VERDANA_KERNING 0.2
  5. #define VERDANA_PADDING 1.0
  6. /*
  7. * verdana11_widths[] has been generated with this method:
  8. * https://github.com/badges/shields/blob/master/measure-text.js
  9. */
  10. static double verdana11_widths[128] = {
  11. [0] = 0.0,
  12. [1] = 0.0,
  13. [2] = 0.0,
  14. [3] = 0.0,
  15. [4] = 0.0,
  16. [5] = 0.0,
  17. [6] = 0.0,
  18. [7] = 0.0,
  19. [8] = 0.0,
  20. [9] = 0.0,
  21. [10] = 0.0,
  22. [11] = 0.0,
  23. [12] = 0.0,
  24. [13] = 0.0,
  25. [14] = 0.0,
  26. [15] = 0.0,
  27. [16] = 0.0,
  28. [17] = 0.0,
  29. [18] = 0.0,
  30. [19] = 0.0,
  31. [20] = 0.0,
  32. [21] = 0.0,
  33. [22] = 0.0,
  34. [23] = 0.0,
  35. [24] = 0.0,
  36. [25] = 0.0,
  37. [26] = 0.0,
  38. [27] = 0.0,
  39. [28] = 0.0,
  40. [29] = 0.0,
  41. [30] = 0.0,
  42. [31] = 0.0,
  43. [32] = 3.8671874999999996, //
  44. [33] = 4.3291015625, // !
  45. [34] = 5.048828125, // "
  46. [35] = 9.001953125, // #
  47. [36] = 6.9931640625, // $
  48. [37] = 11.837890625, // %
  49. [38] = 7.992187499999999, // &
  50. [39] = 2.9541015625, // '
  51. [40] = 4.9951171875, // (
  52. [41] = 4.9951171875, // )
  53. [42] = 6.9931640625, // *
  54. [43] = 9.001953125, // +
  55. [44] = 4.00146484375, // ,
  56. [45] = 4.9951171875, // -
  57. [46] = 4.00146484375, // .
  58. [47] = 4.9951171875, // /
  59. [48] = 6.9931640625, // 0
  60. [49] = 6.9931640625, // 1
  61. [50] = 6.9931640625, // 2
  62. [51] = 6.9931640625, // 3
  63. [52] = 6.9931640625, // 4
  64. [53] = 6.9931640625, // 5
  65. [54] = 6.9931640625, // 6
  66. [55] = 6.9931640625, // 7
  67. [56] = 6.9931640625, // 8
  68. [57] = 6.9931640625, // 9
  69. [58] = 4.9951171875, // :
  70. [59] = 4.9951171875, // ;
  71. [60] = 9.001953125, // <
  72. [61] = 9.001953125, // =
  73. [62] = 9.001953125, // >
  74. [63] = 5.99951171875, // ?
  75. [64] = 11.0, // @
  76. [65] = 7.51953125, // A
  77. [66] = 7.541015625, // B
  78. [67] = 7.680664062499999, // C
  79. [68] = 8.4755859375, // D
  80. [69] = 6.95556640625, // E
  81. [70] = 6.32177734375, // F
  82. [71] = 8.529296875, // G
  83. [72] = 8.26611328125, // H
  84. [73] = 4.6298828125, // I
  85. [74] = 5.00048828125, // J
  86. [75] = 7.62158203125, // K
  87. [76] = 6.123046875, // L
  88. [77] = 9.2705078125, // M
  89. [78] = 8.228515625, // N
  90. [79] = 8.658203125, // O
  91. [80] = 6.63330078125, // P
  92. [81] = 8.658203125, // Q
  93. [82] = 7.6484375, // R
  94. [83] = 7.51953125, // S
  95. [84] = 6.7783203125, // T
  96. [85] = 8.05126953125, // U
  97. [86] = 7.51953125, // V
  98. [87] = 10.87646484375, // W
  99. [88] = 7.53564453125, // X
  100. [89] = 6.767578125, // Y
  101. [90] = 7.53564453125, // Z
  102. [91] = 4.9951171875, // [
  103. [92] = 4.9951171875, // backslash
  104. [93] = 4.9951171875, // ]
  105. [94] = 9.001953125, // ^
  106. [95] = 6.9931640625, // _
  107. [96] = 6.9931640625, // `
  108. [97] = 6.6064453125, // a
  109. [98] = 6.853515625, // b
  110. [99] = 5.73095703125, // c
  111. [100] = 6.853515625, // d
  112. [101] = 6.552734375, // e
  113. [102] = 3.8671874999999996, // f
  114. [103] = 6.853515625, // g
  115. [104] = 6.9609375, // h
  116. [105] = 3.0185546875, // i
  117. [106] = 3.78662109375, // j
  118. [107] = 6.509765625, // k
  119. [108] = 3.0185546875, // l
  120. [109] = 10.69921875, // m
  121. [110] = 6.9609375, // n
  122. [111] = 6.67626953125, // o
  123. [112] = 6.853515625, // p
  124. [113] = 6.853515625, // q
  125. [114] = 4.6943359375, // r
  126. [115] = 5.73095703125, // s
  127. [116] = 4.33447265625, // t
  128. [117] = 6.9609375, // u
  129. [118] = 6.509765625, // v
  130. [119] = 9.001953125, // w
  131. [120] = 6.509765625, // x
  132. [121] = 6.509765625, // y
  133. [122] = 5.779296875, // z
  134. [123] = 6.982421875, // {
  135. [124] = 4.9951171875, // |
  136. [125] = 6.982421875, // }
  137. [126] = 9.001953125, // ~
  138. [127] = 0.0
  139. };
  140. // find the width of the string using the verdana 11points font
  141. static inline double verdana11_width(const char *s, float em_size) {
  142. double w = 0.0;
  143. while(*s) {
  144. // if UTF8 multibyte char found and guess it's width equal 1em
  145. // as label width will be updated with JavaScript this is not so important
  146. // TODO: maybe move UTF8 functions from url.c to separate util in libnetdata
  147. // then use url_utf8_get_byte_length etc.
  148. if(IS_UTF8_STARTBYTE(*s)) {
  149. s++;
  150. while(IS_UTF8_BYTE(*s) && !IS_UTF8_STARTBYTE(*s)){
  151. s++;
  152. }
  153. w += em_size;
  154. }
  155. else {
  156. if(likely(!(*s & 0x80))){ // Byte 1XXX XXXX is not valid in UTF8
  157. double t = verdana11_widths[(unsigned char)*s];
  158. if(t != 0.0)
  159. w += t + VERDANA_KERNING;
  160. }
  161. s++;
  162. }
  163. }
  164. w -= VERDANA_KERNING;
  165. w += VERDANA_PADDING;
  166. return w;
  167. }
  168. static inline size_t escape_xmlz(char *dst, const char *src, size_t len) {
  169. size_t i = len;
  170. // required escapes from
  171. // https://github.com/badges/shields/blob/master/badge.js
  172. while(*src && i) {
  173. switch(*src) {
  174. case '\\':
  175. *dst++ = '/';
  176. src++;
  177. i--;
  178. break;
  179. case '&':
  180. if(i > 5) {
  181. strcpy(dst, "&amp;");
  182. i -= 5;
  183. dst += 5;
  184. src++;
  185. }
  186. else goto cleanup;
  187. break;
  188. case '<':
  189. if(i > 4) {
  190. strcpy(dst, "&lt;");
  191. i -= 4;
  192. dst += 4;
  193. src++;
  194. }
  195. else goto cleanup;
  196. break;
  197. case '>':
  198. if(i > 4) {
  199. strcpy(dst, "&gt;");
  200. i -= 4;
  201. dst += 4;
  202. src++;
  203. }
  204. else goto cleanup;
  205. break;
  206. case '"':
  207. if(i > 6) {
  208. strcpy(dst, "&quot;");
  209. i -= 6;
  210. dst += 6;
  211. src++;
  212. }
  213. else goto cleanup;
  214. break;
  215. case '\'':
  216. if(i > 6) {
  217. strcpy(dst, "&apos;");
  218. i -= 6;
  219. dst += 6;
  220. src++;
  221. }
  222. else goto cleanup;
  223. break;
  224. default:
  225. i--;
  226. *dst++ = *src++;
  227. break;
  228. }
  229. }
  230. cleanup:
  231. *dst = '\0';
  232. return len - i;
  233. }
  234. static inline char *format_value_with_precision_and_unit(char *value_string, size_t value_string_len,
  235. NETDATA_DOUBLE value, const char *units, int precision) {
  236. if(unlikely(isnan(value) || isinf(value)))
  237. value = 0.0;
  238. char *separator = "";
  239. if(unlikely(isalnum(*units)))
  240. separator = " ";
  241. if(precision < 0) {
  242. int len, lstop = 0, trim_zeros = 1;
  243. NETDATA_DOUBLE abs = value;
  244. if(isless(value, 0)) {
  245. lstop = 1;
  246. abs = fabsndd(value);
  247. }
  248. if(isgreaterequal(abs, 1000)) {
  249. len = snprintfz(value_string, value_string_len, "%0.0" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
  250. trim_zeros = 0;
  251. }
  252. else if(isgreaterequal(abs, 10)) len = snprintfz(value_string, value_string_len, "%0.1" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
  253. else if(isgreaterequal(abs, 1)) len = snprintfz(value_string, value_string_len, "%0.2" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
  254. else if(isgreaterequal(abs, 0.1)) len = snprintfz(value_string, value_string_len, "%0.2" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
  255. else if(isgreaterequal(abs, 0.01)) len = snprintfz(value_string, value_string_len, "%0.4" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
  256. else if(isgreaterequal(abs, 0.001)) len = snprintfz(value_string, value_string_len, "%0.5" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
  257. else if(isgreaterequal(abs, 0.0001)) len = snprintfz(value_string, value_string_len, "%0.6" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
  258. else len = snprintfz(value_string, value_string_len, "%0.7" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
  259. if(unlikely(trim_zeros)) {
  260. int l;
  261. // remove trailing zeros from the decimal part
  262. for(l = len - 1; l > lstop; l--) {
  263. if(likely(value_string[l] == '0')) {
  264. value_string[l] = '\0';
  265. len--;
  266. }
  267. else if(unlikely(value_string[l] == '.')) {
  268. value_string[l] = '\0';
  269. len--;
  270. break;
  271. }
  272. else
  273. break;
  274. }
  275. }
  276. if(unlikely(len <= 0)) len = 1;
  277. snprintfz(&value_string[len], value_string_len - len, "%s%s", separator, units);
  278. }
  279. else {
  280. if(precision > 50) precision = 50;
  281. snprintfz(value_string, value_string_len, "%0.*" NETDATA_DOUBLE_MODIFIER "%s%s", precision, (NETDATA_DOUBLE) value, separator, units);
  282. }
  283. return value_string;
  284. }
  285. typedef enum badge_units_format {
  286. UNITS_FORMAT_NONE,
  287. UNITS_FORMAT_SECONDS,
  288. UNITS_FORMAT_SECONDS_AGO,
  289. UNITS_FORMAT_MINUTES,
  290. UNITS_FORMAT_MINUTES_AGO,
  291. UNITS_FORMAT_HOURS,
  292. UNITS_FORMAT_HOURS_AGO,
  293. UNITS_FORMAT_ONOFF,
  294. UNITS_FORMAT_UPDOWN,
  295. UNITS_FORMAT_OKERROR,
  296. UNITS_FORMAT_OKFAILED,
  297. UNITS_FORMAT_EMPTY,
  298. UNITS_FORMAT_PERCENT
  299. } UNITS_FORMAT;
  300. static struct units_formatter {
  301. const char *units;
  302. uint32_t hash;
  303. UNITS_FORMAT format;
  304. } badge_units_formatters[] = {
  305. { "seconds", 0, UNITS_FORMAT_SECONDS },
  306. { "seconds ago", 0, UNITS_FORMAT_SECONDS_AGO },
  307. { "minutes", 0, UNITS_FORMAT_MINUTES },
  308. { "minutes ago", 0, UNITS_FORMAT_MINUTES_AGO },
  309. { "hours", 0, UNITS_FORMAT_HOURS },
  310. { "hours ago", 0, UNITS_FORMAT_HOURS_AGO },
  311. { "on/off", 0, UNITS_FORMAT_ONOFF },
  312. { "on-off", 0, UNITS_FORMAT_ONOFF },
  313. { "onoff", 0, UNITS_FORMAT_ONOFF },
  314. { "up/down", 0, UNITS_FORMAT_UPDOWN },
  315. { "up-down", 0, UNITS_FORMAT_UPDOWN },
  316. { "updown", 0, UNITS_FORMAT_UPDOWN },
  317. { "ok/error", 0, UNITS_FORMAT_OKERROR },
  318. { "ok-error", 0, UNITS_FORMAT_OKERROR },
  319. { "okerror", 0, UNITS_FORMAT_OKERROR },
  320. { "ok/failed", 0, UNITS_FORMAT_OKFAILED },
  321. { "ok-failed", 0, UNITS_FORMAT_OKFAILED },
  322. { "okfailed", 0, UNITS_FORMAT_OKFAILED },
  323. { "empty", 0, UNITS_FORMAT_EMPTY },
  324. { "null", 0, UNITS_FORMAT_EMPTY },
  325. { "percentage", 0, UNITS_FORMAT_PERCENT },
  326. { "percent", 0, UNITS_FORMAT_PERCENT },
  327. { "pcent", 0, UNITS_FORMAT_PERCENT },
  328. // terminator
  329. { NULL, 0, UNITS_FORMAT_NONE }
  330. };
  331. inline char *format_value_and_unit(char *value_string, size_t value_string_len,
  332. NETDATA_DOUBLE value, const char *units, int precision) {
  333. static int max = -1;
  334. int i;
  335. if(unlikely(max == -1)) {
  336. for(i = 0; badge_units_formatters[i].units; i++)
  337. badge_units_formatters[i].hash = simple_hash(badge_units_formatters[i].units);
  338. max = i;
  339. }
  340. if(unlikely(!units)) units = "";
  341. uint32_t hash_units = simple_hash(units);
  342. UNITS_FORMAT format = UNITS_FORMAT_NONE;
  343. for(i = 0; i < max; i++) {
  344. struct units_formatter *ptr = &badge_units_formatters[i];
  345. if(hash_units == ptr->hash && !strcmp(units, ptr->units)) {
  346. format = ptr->format;
  347. break;
  348. }
  349. }
  350. if(unlikely(format == UNITS_FORMAT_SECONDS || format == UNITS_FORMAT_SECONDS_AGO)) {
  351. if(value == 0.0) {
  352. snprintfz(value_string, value_string_len, "%s", "now");
  353. return value_string;
  354. }
  355. else if(isnan(value) || isinf(value)) {
  356. snprintfz(value_string, value_string_len, "%s", "undefined");
  357. return value_string;
  358. }
  359. const char *suffix = (format == UNITS_FORMAT_SECONDS_AGO)?" ago":"";
  360. size_t s = (size_t)value;
  361. size_t d = s / 86400;
  362. s = s % 86400;
  363. size_t h = s / 3600;
  364. s = s % 3600;
  365. size_t m = s / 60;
  366. s = s % 60;
  367. if(d)
  368. snprintfz(value_string, value_string_len, "%zu %s %02zu:%02zu:%02zu%s", d, (d == 1)?"day":"days", h, m, s, suffix);
  369. else
  370. snprintfz(value_string, value_string_len, "%02zu:%02zu:%02zu%s", h, m, s, suffix);
  371. return value_string;
  372. }
  373. else if(unlikely(format == UNITS_FORMAT_MINUTES || format == UNITS_FORMAT_MINUTES_AGO)) {
  374. if(value == 0.0) {
  375. snprintfz(value_string, value_string_len, "%s", "now");
  376. return value_string;
  377. }
  378. else if(isnan(value) || isinf(value)) {
  379. snprintfz(value_string, value_string_len, "%s", "undefined");
  380. return value_string;
  381. }
  382. const char *suffix = (format == UNITS_FORMAT_MINUTES_AGO)?" ago":"";
  383. size_t m = (size_t)value;
  384. size_t d = m / (60 * 24);
  385. m = m % (60 * 24);
  386. size_t h = m / 60;
  387. m = m % 60;
  388. if(d)
  389. snprintfz(value_string, value_string_len, "%zud %02zuh %02zum%s", d, h, m, suffix);
  390. else
  391. snprintfz(value_string, value_string_len, "%zuh %zum%s", h, m, suffix);
  392. return value_string;
  393. }
  394. else if(unlikely(format == UNITS_FORMAT_HOURS || format == UNITS_FORMAT_HOURS_AGO)) {
  395. if(value == 0.0) {
  396. snprintfz(value_string, value_string_len, "%s", "now");
  397. return value_string;
  398. }
  399. else if(isnan(value) || isinf(value)) {
  400. snprintfz(value_string, value_string_len, "%s", "undefined");
  401. return value_string;
  402. }
  403. const char *suffix = (format == UNITS_FORMAT_HOURS_AGO)?" ago":"";
  404. size_t h = (size_t)value;
  405. size_t d = h / 24;
  406. h = h % 24;
  407. if(d)
  408. snprintfz(value_string, value_string_len, "%zud %zuh%s", d, h, suffix);
  409. else
  410. snprintfz(value_string, value_string_len, "%zuh%s", h, suffix);
  411. return value_string;
  412. }
  413. else if(unlikely(format == UNITS_FORMAT_ONOFF)) {
  414. snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"on":"off");
  415. return value_string;
  416. }
  417. else if(unlikely(format == UNITS_FORMAT_UPDOWN)) {
  418. snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"up":"down");
  419. return value_string;
  420. }
  421. else if(unlikely(format == UNITS_FORMAT_OKERROR)) {
  422. snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"error");
  423. return value_string;
  424. }
  425. else if(unlikely(format == UNITS_FORMAT_OKFAILED)) {
  426. snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"failed");
  427. return value_string;
  428. }
  429. else if(unlikely(format == UNITS_FORMAT_EMPTY))
  430. units = "";
  431. else if(unlikely(format == UNITS_FORMAT_PERCENT))
  432. units = "%";
  433. if(unlikely(isnan(value) || isinf(value))) {
  434. strcpy(value_string, "-");
  435. return value_string;
  436. }
  437. return format_value_with_precision_and_unit(value_string, value_string_len, value, units, precision);
  438. }
  439. static struct badge_color {
  440. const char *name;
  441. uint32_t hash;
  442. const char *color;
  443. } badge_colors[] = {
  444. // colors from:
  445. // https://github.com/badges/shields/blob/master/colorscheme.json
  446. { "brightgreen", 0, "4c1" },
  447. { "green", 0, "97CA00" },
  448. { "yellow", 0, "dfb317" },
  449. { "yellowgreen", 0, "a4a61d" },
  450. { "orange", 0, "fe7d37" },
  451. { "red", 0, "e05d44" },
  452. { "blue", 0, "007ec6" },
  453. { "grey", 0, "555" },
  454. { "gray", 0, "555" },
  455. { "lightgrey", 0, "9f9f9f" },
  456. { "lightgray", 0, "9f9f9f" },
  457. // terminator
  458. { NULL, 0, NULL }
  459. };
  460. static inline const char *color_map(const char *color, const char *def) {
  461. static int max = -1;
  462. int i;
  463. if(unlikely(max == -1)) {
  464. for(i = 0; badge_colors[i].name ;i++)
  465. badge_colors[i].hash = simple_hash(badge_colors[i].name);
  466. max = i;
  467. }
  468. uint32_t hash = simple_hash(color);
  469. for(i = 0; i < max; i++) {
  470. struct badge_color *ptr = &badge_colors[i];
  471. if(hash == ptr->hash && !strcmp(color, ptr->name))
  472. return ptr->color;
  473. }
  474. return def;
  475. }
  476. typedef enum color_comparison {
  477. COLOR_COMPARE_EQUAL,
  478. COLOR_COMPARE_NOTEQUAL,
  479. COLOR_COMPARE_LESS,
  480. COLOR_COMPARE_LESSEQUAL,
  481. COLOR_COMPARE_GREATER,
  482. COLOR_COMPARE_GREATEREQUAL,
  483. } BADGE_COLOR_COMPARISON;
  484. static inline void calc_colorz(const char *color, char *final, size_t len, NETDATA_DOUBLE value) {
  485. if(isnan(value) || isinf(value))
  486. value = NAN;
  487. char color_buffer[256 + 1] = "";
  488. char value_buffer[256 + 1] = "";
  489. BADGE_COLOR_COMPARISON comparison = COLOR_COMPARE_GREATER;
  490. // example input:
  491. // color<max|color>min|color:null...
  492. const char *c = color;
  493. while(*c) {
  494. char *dc = color_buffer, *dv = NULL;
  495. size_t ci = 0, vi = 0;
  496. const char *t = c;
  497. while(*t && *t != '|') {
  498. switch(*t) {
  499. case '!':
  500. if(t[1] == '=') t++;
  501. comparison = COLOR_COMPARE_NOTEQUAL;
  502. dv = value_buffer;
  503. break;
  504. case '=':
  505. case ':':
  506. comparison = COLOR_COMPARE_EQUAL;
  507. dv = value_buffer;
  508. break;
  509. case '}':
  510. case ')':
  511. case '>':
  512. if(t[1] == '=') {
  513. comparison = COLOR_COMPARE_GREATEREQUAL;
  514. t++;
  515. }
  516. else
  517. comparison = COLOR_COMPARE_GREATER;
  518. dv = value_buffer;
  519. break;
  520. case '{':
  521. case '(':
  522. case '<':
  523. if(t[1] == '=') {
  524. comparison = COLOR_COMPARE_LESSEQUAL;
  525. t++;
  526. }
  527. else if(t[1] == '>' || t[1] == ')' || t[1] == '}') {
  528. comparison = COLOR_COMPARE_NOTEQUAL;
  529. t++;
  530. }
  531. else
  532. comparison = COLOR_COMPARE_LESS;
  533. dv = value_buffer;
  534. break;
  535. default:
  536. if(dv) {
  537. if(vi < 256) {
  538. vi++;
  539. *dv++ = *t;
  540. }
  541. }
  542. else {
  543. if(ci < 256) {
  544. ci++;
  545. *dc++ = *t;
  546. }
  547. }
  548. break;
  549. }
  550. t++;
  551. }
  552. // prepare for next iteration
  553. if(*t == '|') t++;
  554. c = t;
  555. // do the math
  556. *dc = '\0';
  557. if(dv) {
  558. *dv = '\0';
  559. NETDATA_DOUBLE v;
  560. if(!*value_buffer || !strcmp(value_buffer, "null")) {
  561. v = NAN;
  562. }
  563. else {
  564. v = str2l(value_buffer);
  565. if(isnan(v) || isinf(v))
  566. v = NAN;
  567. }
  568. if(unlikely(isnan(value) || isnan(v))) {
  569. if(isnan(value) && isnan(v))
  570. break;
  571. }
  572. else {
  573. if (unlikely(comparison == COLOR_COMPARE_LESS && isless(value, v))) break;
  574. else if (unlikely(comparison == COLOR_COMPARE_LESSEQUAL && islessequal(value, v))) break;
  575. else if (unlikely(comparison == COLOR_COMPARE_GREATER && isgreater(value, v))) break;
  576. else if (unlikely(comparison == COLOR_COMPARE_GREATEREQUAL && isgreaterequal(value, v))) break;
  577. else if (unlikely(comparison == COLOR_COMPARE_EQUAL && !islessgreater(value, v))) break;
  578. else if (unlikely(comparison == COLOR_COMPARE_NOTEQUAL && islessgreater(value, v))) break;
  579. }
  580. }
  581. else
  582. break;
  583. }
  584. const char *b;
  585. if(color_buffer[0])
  586. b = color_buffer;
  587. else
  588. b = color;
  589. strncpyz(final, b, len);
  590. }
  591. // value + units
  592. #define VALUE_STRING_SIZE 100
  593. // label
  594. #define LABEL_STRING_SIZE 200
  595. // colors
  596. #define COLOR_STRING_SIZE 100
  597. static inline int allowed_hexa_char(char x) {
  598. return ( (x >= '0' && x <= '9') ||
  599. (x >= 'a' && x <= 'f') ||
  600. (x >= 'A' && x <= 'F')
  601. );
  602. }
  603. static int html_color_check(const char *str) {
  604. int i = 0;
  605. while(str[i]) {
  606. if(!allowed_hexa_char(str[i]))
  607. return 0;
  608. if(unlikely(i >= 6))
  609. return 0;
  610. i++;
  611. }
  612. // want to allow either RGB or RRGGBB
  613. return ( i == 6 || i == 3 );
  614. }
  615. // Will parse color arg as #RRGGBB or #RGB or one of the colors
  616. // from color_map hash table
  617. // if parsing fails (argument error) it will return default color
  618. // given as default parameter (def)
  619. // in any case it will return either color in "RRGGBB" or "RGB" format as string
  620. // or whatever is given as def (without checking - caller responsible to give sensible
  621. // safely escaped default) as default if it fails
  622. // in any case this function must always return something we can put directly in XML
  623. // so no escaping is necessary anymore (with exception of default where caller is responsible)
  624. // to give sensible default
  625. #define BADGE_SVG_COLOR_ARG_MAXLEN 20
  626. static const char *parse_color_argument(const char *arg, const char *def)
  627. {
  628. if( !arg )
  629. return def;
  630. size_t len = strnlen(arg, BADGE_SVG_COLOR_ARG_MAXLEN);
  631. if( len < 2 || len >= BADGE_SVG_COLOR_ARG_MAXLEN )
  632. return def;
  633. if( html_color_check(arg) )
  634. return arg;
  635. return color_map(arg, def);
  636. }
  637. void buffer_svg(BUFFER *wb, const char *label,
  638. NETDATA_DOUBLE value, const char *units, const char *label_color, const char *value_color, int precision, int scale, uint32_t options, int fixed_width_lbl, int fixed_width_val, const char* text_color_lbl, const char* text_color_val) {
  639. char value_color_buffer[COLOR_STRING_SIZE + 1]
  640. , value_string[VALUE_STRING_SIZE + 1]
  641. , label_escaped[LABEL_STRING_SIZE + 1]
  642. , value_escaped[VALUE_STRING_SIZE + 1];
  643. const char *label_color_parsed;
  644. const char *value_color_parsed;
  645. double label_width = (double)fixed_width_lbl, value_width = (double)fixed_width_val, total_width;
  646. double height = 20.0, font_size = 11.0, text_offset = 5.8, round_corner = 3.0;
  647. if(scale < 100) scale = 100;
  648. if(unlikely(!value_color || !*value_color))
  649. value_color = (isnan(value) || isinf(value))?"999":"4c1";
  650. calc_colorz(value_color, value_color_buffer, COLOR_STRING_SIZE, value);
  651. format_value_and_unit(value_string, VALUE_STRING_SIZE, (options & RRDR_OPTION_DISPLAY_ABS)? fabsndd(value):value, units, precision);
  652. if(fixed_width_lbl <= 0 || fixed_width_val <= 0) {
  653. label_width = verdana11_width(label, font_size) + (BADGE_HORIZONTAL_PADDING * 2);
  654. value_width = verdana11_width(value_string, font_size) + (BADGE_HORIZONTAL_PADDING * 2);
  655. }
  656. total_width = label_width + value_width;
  657. escape_xmlz(label_escaped, label, LABEL_STRING_SIZE);
  658. escape_xmlz(value_escaped, value_string, VALUE_STRING_SIZE);
  659. label_color_parsed = parse_color_argument(label_color, "555");
  660. value_color_parsed = parse_color_argument(value_color_buffer, "555");
  661. wb->content_type = CT_IMAGE_SVG_XML;
  662. total_width = total_width * scale / 100.0;
  663. height = height * scale / 100.0;
  664. font_size = font_size * scale / 100.0;
  665. text_offset = text_offset * scale / 100.0;
  666. label_width = label_width * scale / 100.0;
  667. value_width = value_width * scale / 100.0;
  668. round_corner = round_corner * scale / 100.0;
  669. // svg template from:
  670. // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
  671. buffer_sprintf(wb,
  672. "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%0.2f\" height=\"%0.2f\">"
  673. "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">"
  674. "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>"
  675. "<stop offset=\"1\" stop-opacity=\".1\"/>"
  676. "</linearGradient>"
  677. "<mask id=\"round\">"
  678. "<rect class=\"bdge-ttl-width\" width=\"%0.2f\" height=\"%0.2f\" rx=\"%0.2f\" fill=\"#fff\"/>"
  679. "</mask>"
  680. "<g mask=\"url(#round)\">"
  681. "<rect class=\"bdge-rect-lbl\" width=\"%0.2f\" height=\"%0.2f\" fill=\"#%s\"/>",
  682. total_width, height,
  683. total_width, height, round_corner,
  684. label_width, height, label_color_parsed); //<rect class="bdge-rect-lbl"
  685. if(fixed_width_lbl > 0 && fixed_width_val > 0) {
  686. buffer_sprintf(wb,
  687. "<clipPath id=\"lbl-rect\">"
  688. "<rect class=\"bdge-rect-lbl\" width=\"%0.2f\" height=\"%0.2f\"/>"
  689. "</clipPath>",
  690. label_width, height); //<clipPath id="lbl-rect"> <rect class="bdge-rect-lbl"
  691. }
  692. buffer_sprintf(wb,
  693. "<rect class=\"bdge-rect-val\" x=\"%0.2f\" width=\"%0.2f\" height=\"%0.2f\" fill=\"#%s\"/>",
  694. label_width, value_width, height, value_color_parsed);
  695. if(fixed_width_lbl > 0 && fixed_width_val > 0) {
  696. buffer_sprintf(wb,
  697. "<clipPath id=\"val-rect\">"
  698. "<rect class=\"bdge-rect-val\" x=\"%0.2f\" width=\"%0.2f\" height=\"%0.2f\"/>"
  699. "</clipPath>",
  700. label_width, value_width, height);
  701. }
  702. buffer_sprintf(wb,
  703. "<rect class=\"bdge-ttl-width\" width=\"%0.2f\" height=\"%0.2f\" fill=\"url(#smooth)\"/>"
  704. "</g>"
  705. "<g text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"%0.2f\">"
  706. "<text class=\"bdge-lbl-lbl\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#010101\" fill-opacity=\".3\" clip-path=\"url(#lbl-rect)\">%s</text>"
  707. "<text class=\"bdge-lbl-lbl\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#%s\" clip-path=\"url(#lbl-rect)\">%s</text>"
  708. "<text class=\"bdge-lbl-val\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#010101\" fill-opacity=\".3\" clip-path=\"url(#val-rect)\">%s</text>"
  709. "<text class=\"bdge-lbl-val\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#%s\" clip-path=\"url(#val-rect)\">%s</text>"
  710. "</g>",
  711. total_width, height,
  712. font_size,
  713. label_width / 2, ceil(height - text_offset), label_escaped,
  714. label_width / 2, ceil(height - text_offset - 1.0), parse_color_argument(text_color_lbl, "fff"), label_escaped,
  715. label_width + value_width / 2 -1, ceil(height - text_offset), value_escaped,
  716. label_width + value_width / 2 -1, ceil(height - text_offset - 1.0), parse_color_argument(text_color_val, "fff"), value_escaped);
  717. if(fixed_width_lbl <= 0 || fixed_width_val <= 0){
  718. buffer_sprintf(wb,
  719. "<script type=\"text/javascript\">"
  720. "var bdg_horiz_padding = %d;"
  721. "function netdata_bdge_each(list, attr, value){"
  722. "Array.prototype.forEach.call(list, function(el){"
  723. "el.setAttribute(attr, value);"
  724. "});"
  725. "};"
  726. "var this_svg = document.currentScript.closest(\"svg\");"
  727. "var elem_lbl = this_svg.getElementsByClassName(\"bdge-lbl-lbl\");"
  728. "var elem_val = this_svg.getElementsByClassName(\"bdge-lbl-val\");"
  729. "var lbl_size = elem_lbl[0].getBBox();"
  730. "var val_size = elem_val[0].getBBox();"
  731. "var width_total = lbl_size.width + bdg_horiz_padding*2;"
  732. "this_svg.getElementsByClassName(\"bdge-rect-lbl\")[0].setAttribute(\"width\", width_total);"
  733. "netdata_bdge_each(elem_lbl, \"x\", (lbl_size.width / 2) + bdg_horiz_padding);"
  734. "netdata_bdge_each(elem_val, \"x\", width_total + (val_size.width / 2) + bdg_horiz_padding);"
  735. "var val_rect = this_svg.getElementsByClassName(\"bdge-rect-val\")[0];"
  736. "val_rect.setAttribute(\"width\", val_size.width + bdg_horiz_padding*2);"
  737. "val_rect.setAttribute(\"x\", width_total);"
  738. "width_total += val_size.width + bdg_horiz_padding*2;"
  739. "var width_update_elems = this_svg.getElementsByClassName(\"bdge-ttl-width\");"
  740. "netdata_bdge_each(width_update_elems, \"width\", width_total);"
  741. "this_svg.setAttribute(\"width\", width_total);"
  742. "</script>",
  743. BADGE_HORIZONTAL_PADDING);
  744. }
  745. buffer_sprintf(wb, "</svg>");
  746. }
  747. #define BADGE_URL_ARG_LBL_COLOR "text_color_lbl"
  748. #define BADGE_URL_ARG_VAL_COLOR "text_color_val"
  749. int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
  750. int ret = HTTP_RESP_BAD_REQUEST;
  751. buffer_flush(w->response.data);
  752. BUFFER *dimensions = NULL;
  753. const char *chart = NULL
  754. , *before_str = NULL
  755. , *after_str = NULL
  756. , *points_str = NULL
  757. , *multiply_str = NULL
  758. , *divide_str = NULL
  759. , *label = NULL
  760. , *units = NULL
  761. , *label_color = NULL
  762. , *value_color = NULL
  763. , *refresh_str = NULL
  764. , *precision_str = NULL
  765. , *scale_str = NULL
  766. , *alarm = NULL
  767. , *fixed_width_lbl_str = NULL
  768. , *fixed_width_val_str = NULL
  769. , *text_color_lbl_str = NULL
  770. , *text_color_val_str = NULL
  771. , *group_options = NULL;
  772. int group = RRDR_GROUPING_AVERAGE;
  773. uint32_t options = 0x00000000;
  774. const RRDCALC_ACQUIRED *rca = NULL;
  775. RRDCALC *rc = NULL;
  776. RRDSET *st = NULL;
  777. while(url) {
  778. char *value = strsep_skip_consecutive_separators(&url, "&");
  779. if(!value || !*value) continue;
  780. char *name = strsep_skip_consecutive_separators(&value, "=");
  781. if(!name || !*name) continue;
  782. if(!value || !*value) continue;
  783. netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value);
  784. // name and value are now the parameters
  785. // they are not null and not empty
  786. if(!strcmp(name, "chart")) chart = value;
  787. else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
  788. if(!dimensions)
  789. dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
  790. buffer_strcat(dimensions, "|");
  791. buffer_strcat(dimensions, value);
  792. }
  793. else if(!strcmp(name, "after")) after_str = value;
  794. else if(!strcmp(name, "before")) before_str = value;
  795. else if(!strcmp(name, "points")) points_str = value;
  796. else if(!strcmp(name, "group_options")) group_options = value;
  797. else if(!strcmp(name, "group")) {
  798. group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
  799. }
  800. else if(!strcmp(name, "options")) {
  801. options |= web_client_api_request_v1_data_options(value);
  802. }
  803. else if(!strcmp(name, "label")) label = value;
  804. else if(!strcmp(name, "units")) units = value;
  805. else if(!strcmp(name, "label_color")) label_color = value;
  806. else if(!strcmp(name, "value_color")) value_color = value;
  807. else if(!strcmp(name, "multiply")) multiply_str = value;
  808. else if(!strcmp(name, "divide")) divide_str = value;
  809. else if(!strcmp(name, "refresh")) refresh_str = value;
  810. else if(!strcmp(name, "precision")) precision_str = value;
  811. else if(!strcmp(name, "scale")) scale_str = value;
  812. else if(!strcmp(name, "fixed_width_lbl")) fixed_width_lbl_str = value;
  813. else if(!strcmp(name, "fixed_width_val")) fixed_width_val_str = value;
  814. else if(!strcmp(name, "alarm")) alarm = value;
  815. else if(!strcmp(name, BADGE_URL_ARG_LBL_COLOR)) text_color_lbl_str = value;
  816. else if(!strcmp(name, BADGE_URL_ARG_VAL_COLOR)) text_color_val_str = value;
  817. }
  818. int fixed_width_lbl = -1;
  819. int fixed_width_val = -1;
  820. if(fixed_width_lbl_str && *fixed_width_lbl_str
  821. && fixed_width_val_str && *fixed_width_val_str) {
  822. fixed_width_lbl = str2i(fixed_width_lbl_str);
  823. fixed_width_val = str2i(fixed_width_val_str);
  824. }
  825. if(!chart || !*chart) {
  826. buffer_no_cacheable(w->response.data);
  827. buffer_sprintf(w->response.data, "No chart id is given at the request.");
  828. goto cleanup;
  829. }
  830. int scale = (scale_str && *scale_str)?str2i(scale_str):100;
  831. st = rrdset_find(host, chart);
  832. if(!st) st = rrdset_find_byname(host, chart);
  833. if(!st) {
  834. buffer_no_cacheable(w->response.data);
  835. buffer_svg(w->response.data, "chart not found", NAN, "", NULL, NULL, -1, scale, 0, -1, -1, NULL, NULL);
  836. ret = HTTP_RESP_OK;
  837. goto cleanup;
  838. }
  839. st->last_accessed_time_s = now_realtime_sec();
  840. if(alarm) {
  841. rca = rrdcalc_from_rrdset_get(st, alarm);
  842. rc = rrdcalc_acquired_to_rrdcalc(rca);
  843. if (!rc) {
  844. buffer_no_cacheable(w->response.data);
  845. buffer_svg(w->response.data, "alarm not found", NAN, "", NULL, NULL, -1, scale, 0, -1, -1, NULL, NULL);
  846. ret = HTTP_RESP_OK;
  847. goto cleanup;
  848. }
  849. }
  850. long long multiply = (multiply_str && *multiply_str )?str2l(multiply_str):1;
  851. long long divide = (divide_str && *divide_str )?str2l(divide_str):1;
  852. long long before = (before_str && *before_str )?str2l(before_str):0;
  853. long long after = (after_str && *after_str )?str2l(after_str):-st->update_every;
  854. int points = (points_str && *points_str )?str2i(points_str):1;
  855. int precision = (precision_str && *precision_str)?str2i(precision_str):-1;
  856. if(!multiply) multiply = 1;
  857. if(!divide) divide = 1;
  858. int refresh = 0;
  859. if(refresh_str && *refresh_str) {
  860. if(!strcmp(refresh_str, "auto")) {
  861. if(rc) refresh = rc->update_every;
  862. else if(options & RRDR_OPTION_NOT_ALIGNED)
  863. refresh = st->update_every;
  864. else {
  865. refresh = (int)(before - after);
  866. if(refresh < 0) refresh = -refresh;
  867. }
  868. }
  869. else {
  870. refresh = str2i(refresh_str);
  871. if(refresh < 0) refresh = -refresh;
  872. }
  873. }
  874. if(!label) {
  875. if(alarm) {
  876. char *s = (char *)alarm;
  877. while(*s) {
  878. if(*s == '_') *s = ' ';
  879. s++;
  880. }
  881. label = alarm;
  882. }
  883. else if(dimensions) {
  884. const char *dim = buffer_tostring(dimensions);
  885. if(*dim == '|') dim++;
  886. label = dim;
  887. }
  888. else
  889. label = rrdset_name(st);
  890. }
  891. if(!units) {
  892. if(alarm) {
  893. if(rc->units)
  894. units = rrdcalc_units(rc);
  895. else
  896. units = "";
  897. }
  898. else if(options & RRDR_OPTION_PERCENTAGE)
  899. units = "%";
  900. else
  901. units = rrdset_units(st);
  902. }
  903. netdata_log_debug(D_WEB_CLIENT, "%llu: API command 'badge.svg' for chart '%s', alarm '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', options '0x%08x'"
  904. , w->id
  905. , chart
  906. , alarm?alarm:""
  907. , (dimensions)?buffer_tostring(dimensions):""
  908. , after
  909. , before
  910. , points
  911. , group
  912. , options
  913. );
  914. if(rc) {
  915. if (refresh > 0) {
  916. buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
  917. w->response.data->date = now_realtime_sec();
  918. w->response.data->expires = w->response.data->date + refresh;
  919. }
  920. else buffer_no_cacheable(w->response.data);
  921. if(!value_color) {
  922. switch(rc->status) {
  923. case RRDCALC_STATUS_CRITICAL:
  924. value_color = "red";
  925. break;
  926. case RRDCALC_STATUS_WARNING:
  927. value_color = "orange";
  928. break;
  929. case RRDCALC_STATUS_CLEAR:
  930. value_color = "brightgreen";
  931. break;
  932. case RRDCALC_STATUS_UNDEFINED:
  933. value_color = "lightgrey";
  934. break;
  935. case RRDCALC_STATUS_UNINITIALIZED:
  936. value_color = "#000";
  937. break;
  938. default:
  939. value_color = "grey";
  940. break;
  941. }
  942. }
  943. buffer_svg(w->response.data,
  944. label,
  945. (isnan(rc->value)||isinf(rc->value)) ? rc->value : rc->value * multiply / divide,
  946. units,
  947. label_color,
  948. value_color,
  949. precision,
  950. scale,
  951. options,
  952. fixed_width_lbl,
  953. fixed_width_val,
  954. text_color_lbl_str,
  955. text_color_val_str
  956. );
  957. ret = HTTP_RESP_OK;
  958. }
  959. else {
  960. time_t latest_timestamp = 0;
  961. int value_is_null = 1;
  962. NETDATA_DOUBLE n = NAN;
  963. ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
  964. // if the collected value is too old, don't calculate its value
  965. if (rrdset_last_entry_s(st) >= (now_realtime_sec() - (st->update_every * gap_when_lost_iterations_above)))
  966. ret = rrdset2value_api_v1(st, w->response.data, &n,
  967. (dimensions) ? buffer_tostring(dimensions) : NULL,
  968. points, after, before, group, group_options, 0, options,
  969. NULL, &latest_timestamp,
  970. NULL, NULL, NULL,
  971. &value_is_null, NULL, 0, 0,
  972. QUERY_SOURCE_API_BADGE, STORAGE_PRIORITY_NORMAL);
  973. // if the value cannot be calculated, show empty badge
  974. if (ret != HTTP_RESP_OK) {
  975. buffer_no_cacheable(w->response.data);
  976. value_is_null = 1;
  977. n = 0;
  978. ret = HTTP_RESP_OK;
  979. }
  980. else if (refresh > 0) {
  981. buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
  982. w->response.data->expires = now_realtime_sec() + refresh;
  983. }
  984. else buffer_no_cacheable(w->response.data);
  985. // render the badge
  986. buffer_svg(w->response.data,
  987. label,
  988. (value_is_null)?NAN:(n * multiply / divide),
  989. units,
  990. label_color,
  991. value_color,
  992. precision,
  993. scale,
  994. options,
  995. fixed_width_lbl,
  996. fixed_width_val,
  997. text_color_lbl_str,
  998. text_color_val_str
  999. );
  1000. }
  1001. cleanup:
  1002. rrdcalc_from_rrdset_release(st, rca);
  1003. buffer_free(dimensions);
  1004. return ret;
  1005. }