]> arthur.barton.de Git - netdata.git/blobdiff - src/web_buffer_svg.c
dns_query_time plugin: replace "." with "_" in dimensions
[netdata.git] / src / web_buffer_svg.c
index 250bbc8985ef8d35a88d8ea73122c633a060960f..2591799d4c6849a047a68525700b23d3156ea637 100644 (file)
@@ -1,20 +1,8 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-#include <math.h>
-#include <string.h>
-#include <ctype.h>
-#include <stdlib.h>
-
 #include "common.h"
-#include "log.h"
-#include "appconfig.h"
-#include "url.h"
-#include "web_buffer.h"
-#include "web_buffer_svg.h"
 
 #define BADGE_HORIZONTAL_PADDING 4
-#define VERDANA_KERNING 0.5
+#define VERDANA_KERNING 0.2
+#define VERDANA_PADDING 1.0
 
 /*
  * verdana11_widths[] has been generated with this method:
 */
 
 double verdana11_widths[256] = {
-       [0] = 0.0,
-       [1] = 0.0,
-       [2] = 0.0,
-       [3] = 0.0,
-       [4] = 0.0,
-       [5] = 0.0,
-       [6] = 0.0,
-       [7] = 0.0,
-       [8] = 0.0,
-       [9] = 0.0,
-       [10] = 0.0,
-       [11] = 0.0,
-       [12] = 0.0,
-       [13] = 0.0,
-       [14] = 0.0,
-       [15] = 0.0,
-       [16] = 0.0,
-       [17] = 0.0,
-       [18] = 0.0,
-       [19] = 0.0,
-       [20] = 0.0,
-       [21] = 0.0,
-       [22] = 0.0,
-       [23] = 0.0,
-       [24] = 0.0,
-       [25] = 0.0,
-       [26] = 0.0,
-       [27] = 0.0,
-       [28] = 0.0,
-       [29] = 0.0,
-       [30] = 0.0,
-       [31] = 0.0,
-       [32] = 3.8671874999999996, //
-       [33] = 4.3291015625, // !
-       [34] = 5.048828125, // "
-       [35] = 9.001953125, // #
-       [36] = 6.9931640625, // $
-       [37] = 11.837890625, // %
-       [38] = 7.992187499999999, // &
-       [39] = 2.9541015625, // '
-       [40] = 4.9951171875, // (
-       [41] = 4.9951171875, // )
-       [42] = 6.9931640625, // *
-       [43] = 9.001953125, // +
-       [44] = 4.00146484375, // ,
-       [45] = 4.9951171875, // -
-       [46] = 4.00146484375, // .
-       [47] = 4.9951171875, // /
-       [48] = 6.9931640625, // 0
-       [49] = 6.9931640625, // 1
-       [50] = 6.9931640625, // 2
-       [51] = 6.9931640625, // 3
-       [52] = 6.9931640625, // 4
-       [53] = 6.9931640625, // 5
-       [54] = 6.9931640625, // 6
-       [55] = 6.9931640625, // 7
-       [56] = 6.9931640625, // 8
-       [57] = 6.9931640625, // 9
-       [58] = 4.9951171875, // :
-       [59] = 4.9951171875, // ;
-       [60] = 9.001953125, // <
-       [61] = 9.001953125, // =
-       [62] = 9.001953125, // >
-       [63] = 5.99951171875, // ?
-       [64] = 11.0, // @
-       [65] = 7.51953125, // A
-       [66] = 7.541015625, // B
-       [67] = 7.680664062499999, // C
-       [68] = 8.4755859375, // D
-       [69] = 6.95556640625, // E
-       [70] = 6.32177734375, // F
-       [71] = 8.529296875, // G
-       [72] = 8.26611328125, // H
-       [73] = 4.6298828125, // I
-       [74] = 5.00048828125, // J
-       [75] = 7.62158203125, // K
-       [76] = 6.123046875, // L
-       [77] = 9.2705078125, // M
-       [78] = 8.228515625, // N
-       [79] = 8.658203125, // O
-       [80] = 6.63330078125, // P
-       [81] = 8.658203125, // Q
-       [82] = 7.6484375, // R
-       [83] = 7.51953125, // S
-       [84] = 6.7783203125, // T
-       [85] = 8.05126953125, // U
-       [86] = 7.51953125, // V
-       [87] = 10.87646484375, // W
-       [88] = 7.53564453125, // X
-       [89] = 6.767578125, // Y
-       [90] = 7.53564453125, // Z
-       [91] = 4.9951171875, // [
-       [92] = 4.9951171875, // backslash
-       [93] = 4.9951171875, // ]
-       [94] = 9.001953125, // ^
-       [95] = 6.9931640625, // _
-       [96] = 6.9931640625, // `
-       [97] = 6.6064453125, // a
-       [98] = 6.853515625, // b
-       [99] = 5.73095703125, // c
-       [100] = 6.853515625, // d
-       [101] = 6.552734375, // e
-       [102] = 3.8671874999999996, // f
-       [103] = 6.853515625, // g
-       [104] = 6.9609375, // h
-       [105] = 3.0185546875, // i
-       [106] = 3.78662109375, // j
-       [107] = 6.509765625, // k
-       [108] = 3.0185546875, // l
-       [109] = 10.69921875, // m
-       [110] = 6.9609375, // n
-       [111] = 6.67626953125, // o
-       [112] = 6.853515625, // p
-       [113] = 6.853515625, // q
-       [114] = 4.6943359375, // r
-       [115] = 5.73095703125, // s
-       [116] = 4.33447265625, // t
-       [117] = 6.9609375, // u
-       [118] = 6.509765625, // v
-       [119] = 9.001953125, // w
-       [120] = 6.509765625, // x
-       [121] = 6.509765625, // y
-       [122] = 5.779296875, // z
-       [123] = 6.982421875, // {
-       [124] = 4.9951171875, // |
-       [125] = 6.982421875, // }
-       [126] = 9.001953125, // ~
-       [127] = 0.0,
-       [128] = 0.0,
-       [129] = 0.0,
-       [130] = 0.0,
-       [131] = 0.0,
-       [132] = 0.0,
-       [133] = 0.0,
-       [134] = 0.0,
-       [135] = 0.0,
-       [136] = 0.0,
-       [137] = 0.0,
-       [138] = 0.0,
-       [139] = 0.0,
-       [140] = 0.0,
-       [141] = 0.0,
-       [142] = 0.0,
-       [143] = 0.0,
-       [144] = 0.0,
-       [145] = 0.0,
-       [146] = 0.0,
-       [147] = 0.0,
-       [148] = 0.0,
-       [149] = 0.0,
-       [150] = 0.0,
-       [151] = 0.0,
-       [152] = 0.0,
-       [153] = 0.0,
-       [154] = 0.0,
-       [155] = 0.0,
-       [156] = 0.0,
-       [157] = 0.0,
-       [158] = 0.0,
-       [159] = 0.0,
-       [160] = 0.0,
-       [161] = 0.0,
-       [162] = 0.0,
-       [163] = 0.0,
-       [164] = 0.0,
-       [165] = 0.0,
-       [166] = 0.0,
-       [167] = 0.0,
-       [168] = 0.0,
-       [169] = 0.0,
-       [170] = 0.0,
-       [171] = 0.0,
-       [172] = 0.0,
-       [173] = 0.0,
-       [174] = 0.0,
-       [175] = 0.0,
-       [176] = 0.0,
-       [177] = 0.0,
-       [178] = 0.0,
-       [179] = 0.0,
-       [180] = 0.0,
-       [181] = 0.0,
-       [182] = 0.0,
-       [183] = 0.0,
-       [184] = 0.0,
-       [185] = 0.0,
-       [186] = 0.0,
-       [187] = 0.0,
-       [188] = 0.0,
-       [189] = 0.0,
-       [190] = 0.0,
-       [191] = 0.0,
-       [192] = 0.0,
-       [193] = 0.0,
-       [194] = 0.0,
-       [195] = 0.0,
-       [196] = 0.0,
-       [197] = 0.0,
-       [198] = 0.0,
-       [199] = 0.0,
-       [200] = 0.0,
-       [201] = 0.0,
-       [202] = 0.0,
-       [203] = 0.0,
-       [204] = 0.0,
-       [205] = 0.0,
-       [206] = 0.0,
-       [207] = 0.0,
-       [208] = 0.0,
-       [209] = 0.0,
-       [210] = 0.0,
-       [211] = 0.0,
-       [212] = 0.0,
-       [213] = 0.0,
-       [214] = 0.0,
-       [215] = 0.0,
-       [216] = 0.0,
-       [217] = 0.0,
-       [218] = 0.0,
-       [219] = 0.0,
-       [220] = 0.0,
-       [221] = 0.0,
-       [222] = 0.0,
-       [223] = 0.0,
-       [224] = 0.0,
-       [225] = 0.0,
-       [226] = 0.0,
-       [227] = 0.0,
-       [228] = 0.0,
-       [229] = 0.0,
-       [230] = 0.0,
-       [231] = 0.0,
-       [232] = 0.0,
-       [233] = 0.0,
-       [234] = 0.0,
-       [235] = 0.0,
-       [236] = 0.0,
-       [237] = 0.0,
-       [238] = 0.0,
-       [239] = 0.0,
-       [240] = 0.0,
-       [241] = 0.0,
-       [242] = 0.0,
-       [243] = 0.0,
-       [244] = 0.0,
-       [245] = 0.0,
-       [246] = 0.0,
-       [247] = 0.0,
-       [248] = 0.0,
-       [249] = 0.0,
-       [250] = 0.0,
-       [251] = 0.0,
-       [252] = 0.0,
-       [253] = 0.0,
-       [254] = 0.0,
-       [255] = 0.0
+    [0] = 0.0,
+    [1] = 0.0,
+    [2] = 0.0,
+    [3] = 0.0,
+    [4] = 0.0,
+    [5] = 0.0,
+    [6] = 0.0,
+    [7] = 0.0,
+    [8] = 0.0,
+    [9] = 0.0,
+    [10] = 0.0,
+    [11] = 0.0,
+    [12] = 0.0,
+    [13] = 0.0,
+    [14] = 0.0,
+    [15] = 0.0,
+    [16] = 0.0,
+    [17] = 0.0,
+    [18] = 0.0,
+    [19] = 0.0,
+    [20] = 0.0,
+    [21] = 0.0,
+    [22] = 0.0,
+    [23] = 0.0,
+    [24] = 0.0,
+    [25] = 0.0,
+    [26] = 0.0,
+    [27] = 0.0,
+    [28] = 0.0,
+    [29] = 0.0,
+    [30] = 0.0,
+    [31] = 0.0,
+    [32] = 3.8671874999999996, //
+    [33] = 4.3291015625, // !
+    [34] = 5.048828125, // "
+    [35] = 9.001953125, // #
+    [36] = 6.9931640625, // $
+    [37] = 11.837890625, // %
+    [38] = 7.992187499999999, // &
+    [39] = 2.9541015625, // '
+    [40] = 4.9951171875, // (
+    [41] = 4.9951171875, // )
+    [42] = 6.9931640625, // *
+    [43] = 9.001953125, // +
+    [44] = 4.00146484375, // ,
+    [45] = 4.9951171875, // -
+    [46] = 4.00146484375, // .
+    [47] = 4.9951171875, // /
+    [48] = 6.9931640625, // 0
+    [49] = 6.9931640625, // 1
+    [50] = 6.9931640625, // 2
+    [51] = 6.9931640625, // 3
+    [52] = 6.9931640625, // 4
+    [53] = 6.9931640625, // 5
+    [54] = 6.9931640625, // 6
+    [55] = 6.9931640625, // 7
+    [56] = 6.9931640625, // 8
+    [57] = 6.9931640625, // 9
+    [58] = 4.9951171875, // :
+    [59] = 4.9951171875, // ;
+    [60] = 9.001953125, // <
+    [61] = 9.001953125, // =
+    [62] = 9.001953125, // >
+    [63] = 5.99951171875, // ?
+    [64] = 11.0, // @
+    [65] = 7.51953125, // A
+    [66] = 7.541015625, // B
+    [67] = 7.680664062499999, // C
+    [68] = 8.4755859375, // D
+    [69] = 6.95556640625, // E
+    [70] = 6.32177734375, // F
+    [71] = 8.529296875, // G
+    [72] = 8.26611328125, // H
+    [73] = 4.6298828125, // I
+    [74] = 5.00048828125, // J
+    [75] = 7.62158203125, // K
+    [76] = 6.123046875, // L
+    [77] = 9.2705078125, // M
+    [78] = 8.228515625, // N
+    [79] = 8.658203125, // O
+    [80] = 6.63330078125, // P
+    [81] = 8.658203125, // Q
+    [82] = 7.6484375, // R
+    [83] = 7.51953125, // S
+    [84] = 6.7783203125, // T
+    [85] = 8.05126953125, // U
+    [86] = 7.51953125, // V
+    [87] = 10.87646484375, // W
+    [88] = 7.53564453125, // X
+    [89] = 6.767578125, // Y
+    [90] = 7.53564453125, // Z
+    [91] = 4.9951171875, // [
+    [92] = 4.9951171875, // backslash
+    [93] = 4.9951171875, // ]
+    [94] = 9.001953125, // ^
+    [95] = 6.9931640625, // _
+    [96] = 6.9931640625, // `
+    [97] = 6.6064453125, // a
+    [98] = 6.853515625, // b
+    [99] = 5.73095703125, // c
+    [100] = 6.853515625, // d
+    [101] = 6.552734375, // e
+    [102] = 3.8671874999999996, // f
+    [103] = 6.853515625, // g
+    [104] = 6.9609375, // h
+    [105] = 3.0185546875, // i
+    [106] = 3.78662109375, // j
+    [107] = 6.509765625, // k
+    [108] = 3.0185546875, // l
+    [109] = 10.69921875, // m
+    [110] = 6.9609375, // n
+    [111] = 6.67626953125, // o
+    [112] = 6.853515625, // p
+    [113] = 6.853515625, // q
+    [114] = 4.6943359375, // r
+    [115] = 5.73095703125, // s
+    [116] = 4.33447265625, // t
+    [117] = 6.9609375, // u
+    [118] = 6.509765625, // v
+    [119] = 9.001953125, // w
+    [120] = 6.509765625, // x
+    [121] = 6.509765625, // y
+    [122] = 5.779296875, // z
+    [123] = 6.982421875, // {
+    [124] = 4.9951171875, // |
+    [125] = 6.982421875, // }
+    [126] = 9.001953125, // ~
+    [127] = 0.0,
+    [128] = 0.0,
+    [129] = 0.0,
+    [130] = 0.0,
+    [131] = 0.0,
+    [132] = 0.0,
+    [133] = 0.0,
+    [134] = 0.0,
+    [135] = 0.0,
+    [136] = 0.0,
+    [137] = 0.0,
+    [138] = 0.0,
+    [139] = 0.0,
+    [140] = 0.0,
+    [141] = 0.0,
+    [142] = 0.0,
+    [143] = 0.0,
+    [144] = 0.0,
+    [145] = 0.0,
+    [146] = 0.0,
+    [147] = 0.0,
+    [148] = 0.0,
+    [149] = 0.0,
+    [150] = 0.0,
+    [151] = 0.0,
+    [152] = 0.0,
+    [153] = 0.0,
+    [154] = 0.0,
+    [155] = 0.0,
+    [156] = 0.0,
+    [157] = 0.0,
+    [158] = 0.0,
+    [159] = 0.0,
+    [160] = 0.0,
+    [161] = 0.0,
+    [162] = 0.0,
+    [163] = 0.0,
+    [164] = 0.0,
+    [165] = 0.0,
+    [166] = 0.0,
+    [167] = 0.0,
+    [168] = 0.0,
+    [169] = 0.0,
+    [170] = 0.0,
+    [171] = 0.0,
+    [172] = 0.0,
+    [173] = 0.0,
+    [174] = 0.0,
+    [175] = 0.0,
+    [176] = 0.0,
+    [177] = 0.0,
+    [178] = 0.0,
+    [179] = 0.0,
+    [180] = 0.0,
+    [181] = 0.0,
+    [182] = 0.0,
+    [183] = 0.0,
+    [184] = 0.0,
+    [185] = 0.0,
+    [186] = 0.0,
+    [187] = 0.0,
+    [188] = 0.0,
+    [189] = 0.0,
+    [190] = 0.0,
+    [191] = 0.0,
+    [192] = 0.0,
+    [193] = 0.0,
+    [194] = 0.0,
+    [195] = 0.0,
+    [196] = 0.0,
+    [197] = 0.0,
+    [198] = 0.0,
+    [199] = 0.0,
+    [200] = 0.0,
+    [201] = 0.0,
+    [202] = 0.0,
+    [203] = 0.0,
+    [204] = 0.0,
+    [205] = 0.0,
+    [206] = 0.0,
+    [207] = 0.0,
+    [208] = 0.0,
+    [209] = 0.0,
+    [210] = 0.0,
+    [211] = 0.0,
+    [212] = 0.0,
+    [213] = 0.0,
+    [214] = 0.0,
+    [215] = 0.0,
+    [216] = 0.0,
+    [217] = 0.0,
+    [218] = 0.0,
+    [219] = 0.0,
+    [220] = 0.0,
+    [221] = 0.0,
+    [222] = 0.0,
+    [223] = 0.0,
+    [224] = 0.0,
+    [225] = 0.0,
+    [226] = 0.0,
+    [227] = 0.0,
+    [228] = 0.0,
+    [229] = 0.0,
+    [230] = 0.0,
+    [231] = 0.0,
+    [232] = 0.0,
+    [233] = 0.0,
+    [234] = 0.0,
+    [235] = 0.0,
+    [236] = 0.0,
+    [237] = 0.0,
+    [238] = 0.0,
+    [239] = 0.0,
+    [240] = 0.0,
+    [241] = 0.0,
+    [242] = 0.0,
+    [243] = 0.0,
+    [244] = 0.0,
+    [245] = 0.0,
+    [246] = 0.0,
+    [247] = 0.0,
+    [248] = 0.0,
+    [249] = 0.0,
+    [250] = 0.0,
+    [251] = 0.0,
+    [252] = 0.0,
+    [253] = 0.0,
+    [254] = 0.0,
+    [255] = 0.0
 };
 
 // find the width of the string using the verdana 11points font
 // re-write the string in place, skiping zero-length characters
 static inline int verdana11_width(char *s) {
-       double w = 0.0;
-       char *d = s;
-
-       while(*s) {
-               double t = verdana11_widths[(unsigned char)*s];
-               if(t == 0.0)
-                       s++;
-               else {
-                       w += t + VERDANA_KERNING;
-                       if(d != s)
-                               *d++ = *s++;
-                       else
-                               d = ++s;
-               }
-       }
-
-       *d = '\0';
-       w -= VERDANA_KERNING;
-       return ceil(w);
+    double w = 0.0;
+    char *d = s;
+
+    while(*s) {
+        double t = verdana11_widths[(unsigned char)*s];
+        if(t == 0.0)
+            s++;
+        else {
+            w += t + VERDANA_KERNING;
+            if(d != s)
+                *d++ = *s++;
+            else
+                d = ++s;
+        }
+    }
+
+    *d = '\0';
+    w -= VERDANA_KERNING;
+    w += VERDANA_PADDING;
+    return (int)ceil(w);
 }
 
 static inline size_t escape_xmlz(char *dst, const char *src, size_t len) {
-       size_t i = len;
-
-       // required escapes from
-       // https://github.com/badges/shields/blob/master/badge.js
-       while(*src && i) {
-               switch(*src) {
-                       case '\\':
-                               *dst++ = '/';
-                               src++;
-                               i--;
-                               break;
-
-                       case '&':
-                               if(i > 5) {
-                                       strcpy(dst, "&amp;");
-                                       i -= 5;
-                                       dst += 5;
-                                       src++;
-                               }
-                               else goto cleanup;
-                               break;
-
-                       case '<':
-                               if(i > 4) {
-                                       strcpy(dst, "&lt;");
-                                       i -= 4;
-                                       dst += 4;
-                                       src++;
-                               }
-                               else goto cleanup;
-                               break;
-
-                       case '>':
-                               if(i > 4) {
-                                       strcpy(dst, "&gt;");
-                                       i -= 4;
-                                       dst += 4;
-                                       src++;
-                               }
-                               else goto cleanup;
-                               break;
-
-                       case '"':
-                               if(i > 6) {
-                                       strcpy(dst, "&quot;");
-                                       i -= 6;
-                                       dst += 6;
-                                       src++;
-                               }
-                               else goto cleanup;
-                               break;
-
-                       case '\'':
-                               if(i > 6) {
-                                       strcpy(dst, "&apos;");
-                                       i -= 6;
-                                       dst += 6;
-                                       src++;
-                               }
-                               else goto cleanup;
-                               break;
-
-                       default:
-                               i--;
-                               *dst++ = *src++;
-                               break;
-               }
-       }
+    size_t i = len;
+
+    // required escapes from
+    // https://github.com/badges/shields/blob/master/badge.js
+    while(*src && i) {
+        switch(*src) {
+            case '\\':
+                *dst++ = '/';
+                src++;
+                i--;
+                break;
+
+            case '&':
+                if(i > 5) {
+                    strcpy(dst, "&amp;");
+                    i -= 5;
+                    dst += 5;
+                    src++;
+                }
+                else goto cleanup;
+                break;
+
+            case '<':
+                if(i > 4) {
+                    strcpy(dst, "&lt;");
+                    i -= 4;
+                    dst += 4;
+                    src++;
+                }
+                else goto cleanup;
+                break;
+
+            case '>':
+                if(i > 4) {
+                    strcpy(dst, "&gt;");
+                    i -= 4;
+                    dst += 4;
+                    src++;
+                }
+                else goto cleanup;
+                break;
+
+            case '"':
+                if(i > 6) {
+                    strcpy(dst, "&quot;");
+                    i -= 6;
+                    dst += 6;
+                    src++;
+                }
+                else goto cleanup;
+                break;
+
+            case '\'':
+                if(i > 6) {
+                    strcpy(dst, "&apos;");
+                    i -= 6;
+                    dst += 6;
+                    src++;
+                }
+                else goto cleanup;
+                break;
+
+            default:
+                i--;
+                *dst++ = *src++;
+                break;
+        }
+    }
 
 cleanup:
-       *dst = '\0';
-       return len - i;
+    *dst = '\0';
+    return len - i;
+}
+
+static inline char *format_value_with_precision_and_unit(char *value_string, size_t value_string_len, calculated_number value, const char *units, int precision) {
+    if(unlikely(isnan(value) || isinf(value)))
+        value = 0.0;
+
+    char *separator = "";
+    if(unlikely(isalnum(*units)))
+        separator = " ";
+
+    if(precision < 0) {
+        int len, lstop = 0, trim_zeros = 1;
+
+        calculated_number abs = value;
+        if(isless(value, 0)) {
+            lstop = 1;
+            abs = -value;
+        }
+
+        if(isgreaterequal(abs, 1000)) {
+            len = snprintfz(value_string, value_string_len, "%0.0Lf", (long double) value);
+            trim_zeros = 0;
+        }
+        else if(isgreaterequal(abs, 10))  len = snprintfz(value_string, value_string_len, "%0.1Lf", (long double) value);
+        else if(isgreaterequal(abs, 1))   len = snprintfz(value_string, value_string_len, "%0.2Lf", (long double) value);
+        else if(isgreaterequal(abs, 0.1)) len = snprintfz(value_string, value_string_len, "%0.2Lf", (long double) value);
+        else                              len = snprintfz(value_string, value_string_len, "%0.4Lf", (long double) value);
+
+        if(unlikely(trim_zeros)) {
+            int l;
+            // remove trailing zeros from the decimal part
+            for(l = len - 1; l > lstop; l--) {
+                if(likely(value_string[l] == '0')) {
+                    value_string[l] = '\0';
+                    len--;
+                }
+
+                else if(unlikely(value_string[l] == '.')) {
+                    value_string[l] = '\0';
+                    len--;
+                    break;
+                }
+
+                else
+                    break;
+            }
+        }
+
+        if(unlikely(len <= 0)) len = 1;
+        snprintfz(&value_string[len], value_string_len - len, "%s%s", separator, units);
+    }
+    else {
+        if(precision > 50) precision = 50;
+        snprintfz(value_string, value_string_len, "%0.*Lf%s%s", precision, (long double) value, separator, units);
+    }
+
+    return value_string;
 }
 
-static inline const char *fix_units(const char *units) {
-       if(!units || !*units || !strcmp(units, "empty") || !strcmp(units, "null")) return "";
-       if(!strcmp(units, "percentage") || !strcmp(units, "percent") || !strcmp(units, "pcent")) return "%";
-       return units;
+inline char *format_value_and_unit(char *value_string, size_t value_string_len, calculated_number value, const char *units, int precision) {
+    static uint32_t
+            hash_seconds = 0,
+            hash_seconds_ago = 0,
+            hash_minutes = 0,
+            hash_minutes_ago = 0,
+            hash_hours = 0,
+            hash_hours_ago = 0,
+            hash_onoff = 0,
+            hash_updown = 0,
+            hash_okerror = 0,
+            hash_okfailed = 0,
+            hash_empty = 0,
+            hash_null = 0,
+            hash_percentage = 0,
+            hash_percent = 0,
+            hash_pcent = 0;
+
+    if(unlikely(!hash_seconds)) {
+        hash_seconds     = simple_hash("seconds");
+        hash_seconds_ago = simple_hash("seconds ago");
+        hash_minutes     = simple_hash("minutes");
+        hash_minutes_ago = simple_hash("minutes ago");
+        hash_hours       = simple_hash("hours");
+        hash_hours_ago   = simple_hash("hours ago");
+        hash_onoff       = simple_hash("on/off");
+        hash_updown      = simple_hash("up/down");
+        hash_okerror     = simple_hash("ok/error");
+        hash_okfailed    = simple_hash("ok/failed");
+        hash_empty       = simple_hash("empty");
+        hash_null        = simple_hash("null");
+        hash_percentage  = simple_hash("percentage");
+        hash_percent     = simple_hash("percent");
+        hash_pcent       = simple_hash("pcent");
+    }
+
+    if(unlikely(!units)) units = "";
+
+    uint32_t hash_units = simple_hash(units);
+
+    if(unlikely((hash_units == hash_seconds && !strcmp(units, "seconds")) || (hash_units == hash_seconds_ago && !strcmp(units, "seconds ago")))) {
+        if(value == 0.0) {
+            snprintfz(value_string, value_string_len, "%s", "now");
+            return value_string;
+        }
+        else if(isnan(value) || isinf(value)) {
+            snprintfz(value_string, value_string_len, "%s", "never");
+            return value_string;
+        }
+
+        const char *suffix = (hash_units == hash_seconds_ago)?" ago":"";
+
+        size_t s = (size_t)value;
+        size_t d = s / 86400;
+        s = s % 86400;
+
+        size_t h = s / 3600;
+        s = s % 3600;
+
+        size_t m = s / 60;
+        s = s % 60;
+
+        if(d)
+            snprintfz(value_string, value_string_len, "%zu %s %02zu:%02zu:%02zu%s", d, (d == 1)?"day":"days", h, m, s, suffix);
+        else
+            snprintfz(value_string, value_string_len, "%02zu:%02zu:%02zu%s", h, m, s, suffix);
+
+        return value_string;
+    }
+
+    else if(unlikely((hash_units == hash_minutes && !strcmp(units, "minutes")) || (hash_units == hash_minutes_ago && !strcmp(units, "minutes ago")))) {
+        if(value == 0.0) {
+            snprintfz(value_string, value_string_len, "%s", "now");
+            return value_string;
+        }
+        else if(isnan(value) || isinf(value)) {
+            snprintfz(value_string, value_string_len, "%s", "never");
+            return value_string;
+        }
+
+        const char *suffix = (hash_units == hash_minutes_ago)?" ago":"";
+
+        size_t m = (size_t)value;
+        size_t d = m / (60 * 24);
+        m = m % (60 * 24);
+
+        size_t h = m / 60;
+        m = m % 60;
+
+        if(d)
+            snprintfz(value_string, value_string_len, "%zud %02zuh %02zum%s", d, h, m, suffix);
+        else
+            snprintfz(value_string, value_string_len, "%zuh %zum%s", h, m, suffix);
+
+        return value_string;
+    }
+
+    else if(unlikely((hash_units == hash_hours && !strcmp(units, "hours")) || (hash_units == hash_hours_ago && !strcmp(units, "hours ago")))) {
+        if(value == 0.0) {
+            snprintfz(value_string, value_string_len, "%s", "now");
+            return value_string;
+        }
+        else if(isnan(value) || isinf(value)) {
+            snprintfz(value_string, value_string_len, "%s", "never");
+            return value_string;
+        }
+
+        const char *suffix = (hash_units == hash_hours_ago)?" ago":"";
+
+        size_t h = (size_t)value;
+        size_t d = h / 24;
+        h = h % 24;
+
+        if(d)
+            snprintfz(value_string, value_string_len, "%zud %zuh%s", d, h, suffix);
+        else
+            snprintfz(value_string, value_string_len, "%zuh%s", h, suffix);
+
+        return value_string;
+    }
+
+    else if(unlikely(hash_units == hash_onoff && !strcmp(units, "on/off"))) {
+        snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"on":"off");
+        return value_string;
+    }
+
+    else if(unlikely(hash_units == hash_updown && !strcmp(units, "up/down"))) {
+        snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"up":"down");
+        return value_string;
+    }
+
+    else if(unlikely(hash_units == hash_okerror && !strcmp(units, "ok/error"))) {
+        snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"error");
+        return value_string;
+    }
+
+    else if(unlikely(hash_units == hash_okfailed && !strcmp(units, "ok/failed"))) {
+        snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"failed");
+        return value_string;
+    }
+
+    else if(unlikely(hash_units == hash_empty && !strcmp(units, "empty")))
+        units = "";
+
+    else if(unlikely(hash_units == hash_null && !strcmp(units, "null")))
+        units = "";
+
+    else if(unlikely(hash_units == hash_percentage && !strcmp(units, "percentage")))
+        units = "%";
+
+    else if(unlikely(hash_units == hash_percent && !strcmp(units, "percent")))
+        units = "%";
+
+    else if(unlikely(hash_units == hash_pcent && !strcmp(units, "pcent")))
+        units = "%";
+
+
+    if(unlikely(isnan(value) || isinf(value))) {
+        strcpy(value_string, "-");
+        return value_string;
+    }
+
+    return format_value_with_precision_and_unit(value_string, value_string_len, value, units, precision);
 }
 
 static inline const char *color_map(const char *color) {
-       // colors from:
-       // https://github.com/badges/shields/blob/master/colorscheme.json
-            if(!strcmp(color, "brightgreen")) return "#4c1";
-       else if(!strcmp(color, "green"))       return "#97CA00";
-       else if(!strcmp(color, "yellow"))      return "#dfb317";
-       else if(!strcmp(color, "yellowgreen")) return "#a4a61d";
-       else if(!strcmp(color, "orange"))      return "#fe7d37";
-       else if(!strcmp(color, "red"))         return "#e05d44";
-       else if(!strcmp(color, "blue"))        return "#007ec6";
-       else if(!strcmp(color, "grey"))        return "#555";
-       else if(!strcmp(color, "gray"))        return "#555";
-       else if(!strcmp(color, "lightgrey"))   return "#9f9f9f";
-       else if(!strcmp(color, "lightgray"))   return "#9f9f9f";
-       return color;
+    // colors from:
+    // https://github.com/badges/shields/blob/master/colorscheme.json
+         if(!strcmp(color, "brightgreen")) return "#4c1";
+    else if(!strcmp(color, "green"))       return "#97CA00";
+    else if(!strcmp(color, "yellow"))      return "#dfb317";
+    else if(!strcmp(color, "yellowgreen")) return "#a4a61d";
+    else if(!strcmp(color, "orange"))      return "#fe7d37";
+    else if(!strcmp(color, "red"))         return "#e05d44";
+    else if(!strcmp(color, "blue"))        return "#007ec6";
+    else if(!strcmp(color, "grey"))        return "#555";
+    else if(!strcmp(color, "gray"))        return "#555";
+    else if(!strcmp(color, "lightgrey"))   return "#9f9f9f";
+    else if(!strcmp(color, "lightgray"))   return "#9f9f9f";
+    return color;
 }
 
-static inline void calc_colorz(const char *color, char *final, size_t len, calculated_number value, int value_is_null) {
-       char color_buffer[256 + 1] = "";
-       char value_buffer[256 + 1] = "";
-       char comparison = '>';
-
-       // example input:
-       // color<max|color>min|color:null...
-
-       const char *c = color;
-       while(*c) {
-               char *dc = color_buffer, *dv = NULL;
-               size_t ci = 0, vi = 0;
-
-               const char *t = c;
-
-               while(*t && *t != '|') {
-                       switch(*t) {
-                               case ':':
-                                       comparison = '=';
-                                       dv = value_buffer;
-                                       break;
-
-                               case '}':
-                               case ')':
-                               case '>':
-                                       if(t[1] == '=') {
-                                               comparison = ')';
-                                               t++;
-                                       }
-                                       else
-                                               comparison = '>';
-                                       dv = value_buffer;
-                                       break;
-
-                               case '{':
-                               case '(':
-                               case '<':
-                                       if(t[1] == '=') {
-                                               comparison = '(';
-                                               t++;
-                                       }
-                                       else
-                                               comparison = '<';
-                                       dv = value_buffer;
-                                       break;
-
-                               default:
-                                       if(dv) {
-                                               if(vi < 256) {
-                                                       vi++;
-                                                       *dv++ = *t;
-                                               }
-                                       }
-                                       else {
-                                               if(ci < 256) {
-                                                       ci++;
-                                                       *dc++ = *t;
-                                               }
-                                       }
-                                       break;
-                       }
-
-                       t++;
-               }
-
-               // prepare for next iteration
-               if(*t == '|') t++;
-               c = t;
-
-               // do the math
-               *dc = '\0';
-               if(dv) {
-                       *dv = '\0';
-
-                       if(value_is_null) {
-                               if(!*value_buffer || !strcmp(value_buffer, "null"))
-                                       break;
-                       }
-                       else {
-                               calculated_number v = strtold(value_buffer, NULL);
-
-                                    if(comparison == '<' && value < v) break;
-                               else if(comparison == '(' && value <= v) break;
-                               else if(comparison == '>' && value > v) break;
-                               else if(comparison == ')' && value >= v) break;
-                               else if(comparison == '=' && value == v) break;
-                       }
-               }
-               else
-                       break;
-       }
-
-       const char *b;
-       if(color_buffer[0])
-               b = color_buffer;
-       else
-               b = color;
-
-       strncpyz(final, b, len);
+static inline void calc_colorz(const char *color, char *final, size_t len, calculated_number value) {
+    int value_is_null = 0;
+    if(isnan(value) || isinf(value)) {
+        value = 0.0;
+        value_is_null = 1;
+    }
+
+    char color_buffer[256 + 1] = "";
+    char value_buffer[256 + 1] = "";
+    char comparison = '>';
+
+    // example input:
+    // color<max|color>min|color:null...
+
+    const char *c = color;
+    while(*c) {
+        char *dc = color_buffer, *dv = NULL;
+        size_t ci = 0, vi = 0;
+
+        const char *t = c;
+
+        while(*t && *t != '|') {
+            switch(*t) {
+                case ':':
+                    comparison = '=';
+                    dv = value_buffer;
+                    break;
+
+                case '}':
+                case ')':
+                case '>':
+                    if(t[1] == '=') {
+                        comparison = ')';
+                        t++;
+                    }
+                    else
+                        comparison = '>';
+                    dv = value_buffer;
+                    break;
+
+                case '{':
+                case '(':
+                case '<':
+                    if(t[1] == '=') {
+                        comparison = '(';
+                        t++;
+                    }
+                    else
+                        comparison = '<';
+                    dv = value_buffer;
+                    break;
+
+                default:
+                    if(dv) {
+                        if(vi < 256) {
+                            vi++;
+                            *dv++ = *t;
+                        }
+                    }
+                    else {
+                        if(ci < 256) {
+                            ci++;
+                            *dc++ = *t;
+                        }
+                    }
+                    break;
+            }
+
+            t++;
+        }
+
+        // prepare for next iteration
+        if(*t == '|') t++;
+        c = t;
+
+        // do the math
+        *dc = '\0';
+        if(dv) {
+            *dv = '\0';
+
+            if(value_is_null) {
+                if(!*value_buffer || !strcmp(value_buffer, "null"))
+                    break;
+            }
+            else {
+                calculated_number v = str2l(value_buffer);
+
+                     if(comparison == '<' && value < v) break;
+                else if(comparison == '(' && value <= v) break;
+                else if(comparison == '>' && value > v) break;
+                else if(comparison == ')' && value >= v) break;
+                else if(comparison == '=' && value == v) break;
+            }
+        }
+        else
+            break;
+    }
+
+    const char *b;
+    if(color_buffer[0])
+        b = color_buffer;
+    else
+        b = color;
+
+    strncpyz(final, b, len);
 }
 
-void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const char *units, const char *label_color, const char *value_color, int value_is_null, int precision) {
-       char label_buffer[256 + 1], value_string[512 + 1], value_color_buffer[256 + 1];
-       char label_escaped[256 + 1], value_escaped[512 + 1], label_color_escaped[256 + 1], value_color_escaped[256 + 1];
-       int label_width, value_width, total_width;
-
-       if(!label_color || !*label_color) label_color = "#555";
-       if(!value_color || !*value_color) value_color = (value_is_null)?"#999":"#4c1";
-
-       units = fix_units(units);
-       calc_colorz(value_color, value_color_buffer, 256, value, value_is_null);
-
-       char *separator = "";
-       if(isalnum(*units)) separator = " ";
-
-       if(value_is_null)
-               strcpy(value_string, "-");
-       else if(precision < 0) {
-               calculated_number abs = (value < (calculated_number)0)?-value:value;
-               if(abs > (calculated_number)1000.0)      snprintfz(value_string, 512, "%0.0Lf%s%s", (long double)value, separator, units);
-               else if(abs > (calculated_number)100.0)  snprintfz(value_string, 512, "%0.1Lf%s%s", (long double)value, separator, units);
-               else if(abs > (calculated_number)1.0)    snprintfz(value_string, 512, "%0.2Lf%s%s", (long double)value, separator, units);
-               else if(abs > (calculated_number)0.1)    snprintfz(value_string, 512, "%0.3Lf%s%s", (long double)value, separator, units);
-               else                                     snprintfz(value_string, 512, "%0.4Lf%s%s", (long double)value, separator, units);
-       }
-       else {
-               if(precision > 50) precision = 50;
-               snprintfz(value_string, 512, "%0.*Lf%s%s", precision, (long double)value, separator, units);
-       }
-
-       // we need to copy the label, since verdana11_width may write to it
-       strncpyz(label_buffer, label, 256);
-
-       label_width = verdana11_width(label_buffer) + (BADGE_HORIZONTAL_PADDING * 2);
-       value_width = verdana11_width(value_string) + (BADGE_HORIZONTAL_PADDING * 2);
-       total_width = label_width + value_width;
-
-       escape_xmlz(label_escaped, label_buffer, 256);
-       escape_xmlz(value_escaped, value_string, 256);
-       escape_xmlz(label_color_escaped, color_map(label_color), 256);
-       escape_xmlz(value_color_escaped, color_map(value_color_buffer), 256);
-
-       wb->contenttype = CT_IMAGE_SVG_XML;
-
-       // svg template from:
-       // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
-       buffer_sprintf(wb,
-               "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%d\" height=\"20\">"
-                       "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">"
-                               "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>"
-                               "<stop offset=\"1\" stop-opacity=\".1\"/>"
-                       "</linearGradient>"
-                       "<mask id=\"round\">"
-                               "<rect width=\"%d\" height=\"20\" rx=\"3\" fill=\"#fff\"/>"
-                       "</mask>"
-                       "<g mask=\"url(#round)\">"
-                               "<rect width=\"%d\" height=\"20\" fill=\"%s\"/>"
-                               "<rect x=\"%d\" width=\"%d\" height=\"20\" fill=\"%s\"/>"
-                               "<rect width=\"%d\" height=\"20\" fill=\"url(#smooth)\"/>"
-                       "</g>"
-                       "<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">"
-                               "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
-                               "<text x=\"%d\" y=\"14\">%s</text>"
-                               "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
-                               "<text x=\"%d\" y=\"14\">%s</text>"
-                       "</g>"
-               "</svg>",
-               total_width, total_width,
-               label_width, label_color_escaped,
-               label_width, value_width, value_color_escaped,
-               total_width,
-               label_width / 2, label_escaped,
-               label_width / 2, label_escaped,
-               label_width + value_width / 2 -1, value_escaped,
-               label_width + value_width / 2 -1, value_escaped);
+// value + units
+#define VALUE_STRING_SIZE 100
+
+// label
+#define LABEL_STRING_SIZE 200
+
+// colors
+#define COLOR_STRING_SIZE 100
+
+void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const char *units, const char *label_color, const char *value_color, int precision) {
+    char      label_buffer[LABEL_STRING_SIZE + 1]
+            , value_color_buffer[COLOR_STRING_SIZE + 1]
+            , value_string[VALUE_STRING_SIZE + 1]
+            , label_escaped[LABEL_STRING_SIZE + 1]
+            , value_escaped[VALUE_STRING_SIZE + 1]
+            , label_color_escaped[COLOR_STRING_SIZE + 1]
+            , value_color_escaped[COLOR_STRING_SIZE + 1];
+
+    int label_width, value_width, total_width;
+
+    if(unlikely(!label_color || !*label_color))
+        label_color = "#555";
+
+    if(unlikely(!value_color || !*value_color))
+        value_color = (isnan(value) || isinf(value))?"#999":"#4c1";
+
+    calc_colorz(value_color, value_color_buffer, COLOR_STRING_SIZE, value);
+    format_value_and_unit(value_string, VALUE_STRING_SIZE, value, units, precision);
+
+    // we need to copy the label, since verdana11_width may write to it
+    strncpyz(label_buffer, label, LABEL_STRING_SIZE);
+
+    label_width = verdana11_width(label_buffer) + (BADGE_HORIZONTAL_PADDING * 2);
+    value_width = verdana11_width(value_string) + (BADGE_HORIZONTAL_PADDING * 2);
+    total_width = label_width + value_width;
+
+    escape_xmlz(label_escaped, label_buffer, LABEL_STRING_SIZE);
+    escape_xmlz(value_escaped, value_string, VALUE_STRING_SIZE);
+    escape_xmlz(label_color_escaped, color_map(label_color), COLOR_STRING_SIZE);
+    escape_xmlz(value_color_escaped, color_map(value_color_buffer), COLOR_STRING_SIZE);
+
+    wb->contenttype = CT_IMAGE_SVG_XML;
+
+    // svg template from:
+    // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
+    buffer_sprintf(wb,
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%d\" height=\"20\">"
+            "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">"
+                "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>"
+                "<stop offset=\"1\" stop-opacity=\".1\"/>"
+            "</linearGradient>"
+            "<mask id=\"round\">"
+                "<rect width=\"%d\" height=\"20\" rx=\"3\" fill=\"#fff\"/>"
+            "</mask>"
+            "<g mask=\"url(#round)\">"
+                "<rect width=\"%d\" height=\"20\" fill=\"%s\"/>"
+                "<rect x=\"%d\" width=\"%d\" height=\"20\" fill=\"%s\"/>"
+                "<rect width=\"%d\" height=\"20\" fill=\"url(#smooth)\"/>"
+            "</g>"
+            "<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">"
+                "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
+                "<text x=\"%d\" y=\"14\">%s</text>"
+                "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
+                "<text x=\"%d\" y=\"14\">%s</text>"
+            "</g>"
+        "</svg>",
+        total_width, total_width,
+        label_width, label_color_escaped,
+        label_width, value_width, value_color_escaped,
+        total_width,
+        label_width / 2, label_escaped,
+        label_width / 2, label_escaped,
+        label_width + value_width / 2 -1, value_escaped,
+        label_width + value_width / 2 -1, value_escaped);
 }