diff options
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; +} |