summaryrefslogtreecommitdiffstats
path: root/web/api/badges/web_buffer_svg.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--web/api/badges/web_buffer_svg.c (renamed from src/web_buffer_svg.c)458
1 files changed, 384 insertions, 74 deletions
diff --git a/src/web_buffer_svg.c b/web/api/badges/web_buffer_svg.c
index c05e526ed..d0600359e 100644
--- a/src/web_buffer_svg.c
+++ b/web/api/badges/web_buffer_svg.c
@@ -1,4 +1,6 @@
-#include "common.h"
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "web_buffer_svg.h"
#define BADGE_HORIZONTAL_PADDING 4
#define VERDANA_KERNING 0.2
@@ -9,7 +11,7 @@
* https://github.com/badges/shields/blob/master/measure-text.js
*/
-double verdana11_widths[256] = {
+static double verdana11_widths[256] = {
[0] = 0.0,
[1] = 0.0,
[2] = 0.0,
@@ -428,57 +430,91 @@ static inline char *format_value_with_precision_and_unit(char *value_string, siz
return value_string;
}
+typedef enum badge_units_format {
+ UNITS_FORMAT_NONE,
+ UNITS_FORMAT_SECONDS,
+ UNITS_FORMAT_SECONDS_AGO,
+ UNITS_FORMAT_MINUTES,
+ UNITS_FORMAT_MINUTES_AGO,
+ UNITS_FORMAT_HOURS,
+ UNITS_FORMAT_HOURS_AGO,
+ UNITS_FORMAT_ONOFF,
+ UNITS_FORMAT_UPDOWN,
+ UNITS_FORMAT_OKERROR,
+ UNITS_FORMAT_OKFAILED,
+ UNITS_FORMAT_EMPTY,
+ UNITS_FORMAT_PERCENT
+} UNITS_FORMAT;
+
+
+static struct units_formatter {
+ const char *units;
+ uint32_t hash;
+ UNITS_FORMAT format;
+} badge_units_formatters[] = {
+ { "seconds", 0, UNITS_FORMAT_SECONDS },
+ { "seconds ago", 0, UNITS_FORMAT_SECONDS_AGO },
+ { "minutes", 0, UNITS_FORMAT_MINUTES },
+ { "minutes ago", 0, UNITS_FORMAT_MINUTES_AGO },
+ { "hours", 0, UNITS_FORMAT_HOURS },
+ { "hours ago", 0, UNITS_FORMAT_HOURS_AGO },
+ { "on/off", 0, UNITS_FORMAT_ONOFF },
+ { "on-off", 0, UNITS_FORMAT_ONOFF },
+ { "onoff", 0, UNITS_FORMAT_ONOFF },
+ { "up/down", 0, UNITS_FORMAT_UPDOWN },
+ { "up-down", 0, UNITS_FORMAT_UPDOWN },
+ { "updown", 0, UNITS_FORMAT_UPDOWN },
+ { "ok/error", 0, UNITS_FORMAT_OKERROR },
+ { "ok-error", 0, UNITS_FORMAT_OKERROR },
+ { "okerror", 0, UNITS_FORMAT_OKERROR },
+ { "ok/failed", 0, UNITS_FORMAT_OKFAILED },
+ { "ok-failed", 0, UNITS_FORMAT_OKFAILED },
+ { "okfailed", 0, UNITS_FORMAT_OKFAILED },
+ { "empty", 0, UNITS_FORMAT_EMPTY },
+ { "null", 0, UNITS_FORMAT_EMPTY },
+ { "percentage", 0, UNITS_FORMAT_PERCENT },
+ { "percent", 0, UNITS_FORMAT_PERCENT },
+ { "pcent", 0, UNITS_FORMAT_PERCENT },
+
+ // terminator
+ { NULL, 0, UNITS_FORMAT_NONE }
+};
+
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");
+ static int max = -1;
+ int i;
+
+ if(unlikely(max == -1)) {
+ for(i = 0; badge_units_formatters[i].units; i++)
+ badge_units_formatters[i].hash = simple_hash(badge_units_formatters[i].units);
+
+ max = i;
}
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")))) {
+ UNITS_FORMAT format = UNITS_FORMAT_NONE;
+ for(i = 0; i < max; i++) {
+ struct units_formatter *ptr = &badge_units_formatters[i];
+
+ if(hash_units == ptr->hash && !strcmp(units, ptr->units)) {
+ format = ptr->format;
+ break;
+ }
+ }
+
+ if(unlikely(format == UNITS_FORMAT_SECONDS || format == UNITS_FORMAT_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");
+ snprintfz(value_string, value_string_len, "%s", "undefined");
return value_string;
}
- const char *suffix = (hash_units == hash_seconds_ago)?" ago":"";
+ const char *suffix = (format == UNITS_FORMAT_SECONDS_AGO)?" ago":"";
size_t s = (size_t)value;
size_t d = s / 86400;
@@ -498,17 +534,17 @@ inline char *format_value_and_unit(char *value_string, size_t value_string_len,
return value_string;
}
- else if(unlikely((hash_units == hash_minutes && !strcmp(units, "minutes")) || (hash_units == hash_minutes_ago && !strcmp(units, "minutes ago")))) {
+ else if(unlikely(format == UNITS_FORMAT_MINUTES || format == UNITS_FORMAT_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");
+ snprintfz(value_string, value_string_len, "%s", "undefined");
return value_string;
}
- const char *suffix = (hash_units == hash_minutes_ago)?" ago":"";
+ const char *suffix = (format == UNITS_FORMAT_MINUTES_AGO)?" ago":"";
size_t m = (size_t)value;
size_t d = m / (60 * 24);
@@ -525,17 +561,17 @@ inline char *format_value_and_unit(char *value_string, size_t value_string_len,
return value_string;
}
- else if(unlikely((hash_units == hash_hours && !strcmp(units, "hours")) || (hash_units == hash_hours_ago && !strcmp(units, "hours ago")))) {
+ else if(unlikely(format == UNITS_FORMAT_HOURS || format == UNITS_FORMAT_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");
+ snprintfz(value_string, value_string_len, "%s", "undefined");
return value_string;
}
- const char *suffix = (hash_units == hash_hours_ago)?" ago":"";
+ const char *suffix = (format == UNITS_FORMAT_HOURS_AGO)?" ago":"";
size_t h = (size_t)value;
size_t d = h / 24;
@@ -549,42 +585,32 @@ inline char *format_value_and_unit(char *value_string, size_t value_string_len,
return value_string;
}
- else if(unlikely(hash_units == hash_onoff && !strcmp(units, "on/off"))) {
+ else if(unlikely(format == UNITS_FORMAT_ONOFF)) {
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"))) {
+ else if(unlikely(format == UNITS_FORMAT_UPDOWN)) {
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"))) {
+ else if(unlikely(format == UNITS_FORMAT_OKERROR)) {
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"))) {
+ else if(unlikely(format == UNITS_FORMAT_OKFAILED)) {
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")))
+ else if(unlikely(format == UNITS_FORMAT_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")))
+ else if(unlikely(format == UNITS_FORMAT_PERCENT))
units = "%";
-
if(unlikely(isnan(value) || isinf(value))) {
strcpy(value_string, "-");
return value_string;
@@ -593,20 +619,51 @@ inline char *format_value_and_unit(char *value_string, size_t value_string_len,
return format_value_with_precision_and_unit(value_string, value_string_len, value, units, precision);
}
+static struct badge_color {
+ const char *name;
+ uint32_t hash;
+ const char *color;
+} badge_colors[] = {
+
+ // colors from:
+ // https://github.com/badges/shields/blob/master/colorscheme.json
+
+ { "brightgreen", 0, "#4c1" },
+ { "green", 0, "#97CA00" },
+ { "yellow", 0, "#dfb317" },
+ { "yellowgreen", 0, "#a4a61d" },
+ { "orange", 0, "#fe7d37" },
+ { "red", 0, "#e05d44" },
+ { "blue", 0, "#007ec6" },
+ { "grey", 0, "#555" },
+ { "gray", 0, "#555" },
+ { "lightgrey", 0, "#9f9f9f" },
+ { "lightgray", 0, "#9f9f9f" },
+
+ // terminator
+ { NULL, 0, NULL }
+};
+
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";
+ static int max = -1;
+ int i;
+
+ if(unlikely(max == -1)) {
+ for(i = 0; badge_colors[i].name ;i++)
+ badge_colors[i].hash = simple_hash(badge_colors[i].name);
+
+ max = i;
+ }
+
+ uint32_t hash = simple_hash(color);
+
+ for(i = 0; i < max; i++) {
+ struct badge_color *ptr = &badge_colors[i];
+
+ if(hash == ptr->hash && !strcmp(color, ptr->name))
+ return ptr->color;
+ }
+
return color;
}
@@ -830,3 +887,256 @@ void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const ch
label_width + value_width / 2 -1, ceil(height - text_offset), value_escaped,
label_width + value_width / 2 -1, ceil(height - text_offset - 1.0), value_escaped);
}
+
+int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
+ int ret = 400;
+ buffer_flush(w->response.data);
+
+ BUFFER *dimensions = NULL;
+
+ const char *chart = NULL
+ , *before_str = NULL
+ , *after_str = NULL
+ , *points_str = NULL
+ , *multiply_str = NULL
+ , *divide_str = NULL
+ , *label = NULL
+ , *units = NULL
+ , *label_color = NULL
+ , *value_color = NULL
+ , *refresh_str = NULL
+ , *precision_str = NULL
+ , *scale_str = NULL
+ , *alarm = NULL;
+
+ int group = RRDR_GROUPING_AVERAGE;
+ uint32_t options = 0x00000000;
+
+ while(url) {
+ char *value = mystrsep(&url, "/?&");
+ if(!value || !*value) continue;
+
+ char *name = mystrsep(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value);
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "chart")) chart = value;
+ else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
+ if(!dimensions)
+ dimensions = buffer_create(100);
+
+ buffer_strcat(dimensions, "|");
+ buffer_strcat(dimensions, value);
+ }
+ else if(!strcmp(name, "after")) after_str = value;
+ else if(!strcmp(name, "before")) before_str = value;
+ else if(!strcmp(name, "points")) points_str = value;
+ else if(!strcmp(name, "group")) {
+ group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE);
+ }
+ else if(!strcmp(name, "options")) {
+ options |= web_client_api_request_v1_data_options(value);
+ }
+ else if(!strcmp(name, "label")) label = value;
+ else if(!strcmp(name, "units")) units = value;
+ else if(!strcmp(name, "label_color")) label_color = value;
+ else if(!strcmp(name, "value_color")) value_color = value;
+ else if(!strcmp(name, "multiply")) multiply_str = value;
+ else if(!strcmp(name, "divide")) divide_str = value;
+ else if(!strcmp(name, "refresh")) refresh_str = value;
+ else if(!strcmp(name, "precision")) precision_str = value;
+ else if(!strcmp(name, "scale")) scale_str = value;
+ else if(!strcmp(name, "alarm")) alarm = value;
+ }
+
+ if(!chart || !*chart) {
+ buffer_no_cacheable(w->response.data);
+ buffer_sprintf(w->response.data, "No chart id is given at the request.");
+ goto cleanup;
+ }
+
+ int scale = (scale_str && *scale_str)?str2i(scale_str):100;
+
+ RRDSET *st = rrdset_find(host, chart);
+ if(!st) st = rrdset_find_byname(host, chart);
+ if(!st) {
+ buffer_no_cacheable(w->response.data);
+ buffer_svg(w->response.data, "chart not found", NAN, "", NULL, NULL, -1, scale, 0);
+ ret = 200;
+ goto cleanup;
+ }
+ st->last_accessed_time = now_realtime_sec();
+
+ RRDCALC *rc = NULL;
+ if(alarm) {
+ rc = rrdcalc_find(st, alarm);
+ if (!rc) {
+ buffer_no_cacheable(w->response.data);
+ buffer_svg(w->response.data, "alarm not found", NAN, "", NULL, NULL, -1, scale, 0);
+ ret = 200;
+ goto cleanup;
+ }
+ }
+
+ long long multiply = (multiply_str && *multiply_str )?str2l(multiply_str):1;
+ long long divide = (divide_str && *divide_str )?str2l(divide_str):1;
+ long long before = (before_str && *before_str )?str2l(before_str):0;
+ long long after = (after_str && *after_str )?str2l(after_str):-st->update_every;
+ int points = (points_str && *points_str )?str2i(points_str):1;
+ int precision = (precision_str && *precision_str)?str2i(precision_str):-1;
+
+ if(!multiply) multiply = 1;
+ if(!divide) divide = 1;
+
+ int refresh = 0;
+ if(refresh_str && *refresh_str) {
+ if(!strcmp(refresh_str, "auto")) {
+ if(rc) refresh = rc->update_every;
+ else if(options & RRDR_OPTION_NOT_ALIGNED)
+ refresh = st->update_every;
+ else {
+ refresh = (int)(before - after);
+ if(refresh < 0) refresh = -refresh;
+ }
+ }
+ else {
+ refresh = str2i(refresh_str);
+ if(refresh < 0) refresh = -refresh;
+ }
+ }
+
+ if(!label) {
+ if(alarm) {
+ char *s = (char *)alarm;
+ while(*s) {
+ if(*s == '_') *s = ' ';
+ s++;
+ }
+ label = alarm;
+ }
+ else if(dimensions) {
+ const char *dim = buffer_tostring(dimensions);
+ if(*dim == '|') dim++;
+ label = dim;
+ }
+ else
+ label = st->name;
+ }
+ if(!units) {
+ if(alarm) {
+ if(rc->units)
+ units = rc->units;
+ else
+ units = "";
+ }
+ else if(options & RRDR_OPTION_PERCENTAGE)
+ units = "%";
+ else
+ units = st->units;
+ }
+
+ 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'"
+ , w->id
+ , chart
+ , alarm?alarm:""
+ , (dimensions)?buffer_tostring(dimensions):""
+ , after
+ , before
+ , points
+ , group
+ , options
+ );
+
+ if(rc) {
+ if (refresh > 0) {
+ buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
+ w->response.data->expires = now_realtime_sec() + refresh;
+ }
+ else buffer_no_cacheable(w->response.data);
+
+ if(!value_color) {
+ switch(rc->status) {
+ case RRDCALC_STATUS_CRITICAL:
+ value_color = "red";
+ break;
+
+ case RRDCALC_STATUS_WARNING:
+ value_color = "orange";
+ break;
+
+ case RRDCALC_STATUS_CLEAR:
+ value_color = "brightgreen";
+ break;
+
+ case RRDCALC_STATUS_UNDEFINED:
+ value_color = "lightgrey";
+ break;
+
+ case RRDCALC_STATUS_UNINITIALIZED:
+ value_color = "#000";
+ break;
+
+ default:
+ value_color = "grey";
+ break;
+ }
+ }
+
+ buffer_svg(w->response.data,
+ label,
+ (isnan(rc->value)||isinf(rc->value)) ? rc->value : rc->value * multiply / divide,
+ units,
+ label_color,
+ value_color,
+ precision,
+ scale,
+ options
+ );
+ ret = 200;
+ }
+ else {
+ time_t latest_timestamp = 0;
+ int value_is_null = 1;
+ calculated_number n = NAN;
+ ret = 500;
+
+ // if the collected value is too old, don't calculate its value
+ if (rrdset_last_entry_t(st) >= (now_realtime_sec() - (st->update_every * st->gap_when_lost_iterations_above)))
+ ret = rrdset2value_api_v1(st, w->response.data, &n, (dimensions) ? buffer_tostring(dimensions) : NULL
+ , points, after, before, group, 0, options, NULL, &latest_timestamp, &value_is_null);
+
+ // if the value cannot be calculated, show empty badge
+ if (ret != 200) {
+ buffer_no_cacheable(w->response.data);
+ value_is_null = 1;
+ n = 0;
+ ret = 200;
+ }
+ else if (refresh > 0) {
+ buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
+ w->response.data->expires = now_realtime_sec() + refresh;
+ }
+ else buffer_no_cacheable(w->response.data);
+
+ // render the badge
+ buffer_svg(w->response.data,
+ label,
+ (value_is_null)?NAN:(n * multiply / divide),
+ units,
+ label_color,
+ value_color,
+ precision,
+ scale,
+ options
+ );
+ }
+
+ cleanup:
+ buffer_free(dimensions);
+ return ret;
+}