From 0ce63f2b9ce74a2a71f2707c1cf5294c7fa2537b Mon Sep 17 00:00:00 2001 From: Etienne Buira Date: Mon, 16 Mar 2015 22:23:55 +0100 Subject: [PATCH] rrd_graph: Add human scale durations pretty-print --- doc/rrdgraph.pod | 10 +++ doc/rrdgraph_graph.pod | 62 +++++++++++++- src/rrd_graph.c | 184 +++++++++++++++++++++++++++++++++++++++++ src/rrd_graph.h | 1 + src/rrd_graph_helper.c | 5 ++ tests/vformatter1 | 6 ++ 6 files changed, 265 insertions(+), 3 deletions(-) diff --git a/doc/rrdgraph.pod b/doc/rrdgraph.pod index 0e7802a9..a675a6cd 100644 --- a/doc/rrdgraph.pod +++ b/doc/rrdgraph.pod @@ -229,6 +229,11 @@ Values are interpreted as unix timestamps (number of seconds since 1970-01-01 00:00:00 UTC) and expressed using strftime format (default is '%Y-%m-%d %H:%M:%S'). See also B<--units-length> and B<--left-axis-format>. +=item B + +Values are interpreted as duration in milliseconds. Formatting follows the rules +of valstrfduration qualified PRINT/GPRINT. See L. + =back [B<--left-axis-format> I] @@ -305,6 +310,11 @@ Values are interpreted as unix timestamps (number of seconds since 1970-01-01 00:00:00 UTC) and expressed using strftime format (default is '%Y-%m-%d %H:%M:%S'). See also B<--units-length> and B<--right-axis-format>. +=item B + +Values are interpreted as duration in milliseconds. Formatting follows the rules +of valstrfduration qualified PRINT/GPRINT. See L. + =back diff --git a/doc/rrdgraph_graph.pod b/doc/rrdgraph_graph.pod index 183ee39c..eb0863d3 100644 --- a/doc/rrdgraph_graph.pod +++ b/doc/rrdgraph_graph.pod @@ -6,7 +6,7 @@ rrdgraph_graph - rrdtool graph command reference =head1 SYNOPSIS -BB<:>IB<:>I[B<:strftime>|B<:valstrftime>] +BB<:>IB<:>I[B<:strftime>|B<:valstrftime>|B<:valstrfduration>] BB<:>IB<:>I @@ -40,9 +40,9 @@ Similarly, no report is generated if you don't use print options. =head2 PRINT -=head3 BIB<:>I[B<:strftime>|B<:valstrftime>] +=head3 BIB<:>I[B<:strftime>|B<:valstrftime>|B<:valstrfduration>] -Depending on the context, either the value component (no suffix or valstrftime) +Depending on the context, either the value component (no suffix, valstrftime or valstrfduration) or the time component (strftime) of a B is printed using I. It is an error to specify a I generated by a B or B. @@ -197,6 +197,62 @@ A literal `%' character. =back +Formatting values as duration is done using printf like conversion specifications: + + - All non-conversion specification chars are copied unchanged + - A conversion specification has format '%' [ ['0'] minwidth ] [ '.' precision ] conversion-specifier + +With conversion-specifier being one of: + +=over + +=item B<%> + +A raw '%' is output, width and precision are ignored + +=item B + +Number of weeks + +=item B + +Number of days, modulus number of weeks + +=item B + +Number of days + +=item B + +Number of hours, modulus number of days + +=item B + +Number of hours + +=item B + +Number of minutes, modulus number of hours + +=item B + +Number of minutes + +=item B + +Number of seconds, modulus number of minutes + +=item B + +Number of seconds + +=item B + +Number of milliseconds, modulus seconds + +=back + + =head3 BIB<:>IB<:>I I diff --git a/src/rrd_graph.c b/src/rrd_graph.c index eaddea0d..95130cb1 100644 --- a/src/rrd_graph.c +++ b/src/rrd_graph.c @@ -226,6 +226,8 @@ gfx_color_t graph_col[] = /* default colors */ const char default_timestamp_fmt[] = "%Y-%m-%d %H:%M:%S"; +const char default_duration_fmt[] = "%H:%02m:%02s"; + /* #define DEBUG */ @@ -1633,6 +1635,129 @@ time_t find_next_time( } +static int strfduration(char * const dest, const size_t destlen, const char * const fmt, const double duration) +{ + char *d = dest, * const dbound = dest + destlen; + const char *f; + int wlen = 0; + double seconds = fabs(duration) / 1000.0, + minutes = seconds / 60.0, + hours = minutes / 60.0, + days = hours / 24.0, + weeks = days / 7.0; + +#define STORC(chr) do { \ + if (wlen == INT_MAX) return -1; \ + wlen++; \ + if (d < dbound) \ + *d++ = (chr); \ +} while(0); + +#define STORPF(valArg) do { \ + double pval = trunc((valArg) * pow(10.0, precision)) / pow(10.0, precision); \ + char *tmpfmt; \ + ptrdiff_t avail = dbound - d; \ + int r; \ +\ + if (avail < 0 || (uintmax_t) avail > SIZE_MAX) return -1; \ +\ + tmpfmt = sprintf_alloc("%%%s%d.%df", \ + zpad ? "0" : "", \ + width, \ + precision); \ + if (!tmpfmt) return -1; \ +\ + r = snprintf(d, avail, tmpfmt, pval); \ + free(tmpfmt); \ + if (r < 0) return -1; \ + d += min(avail, r); \ + if (INT_MAX-r < wlen) return -1; \ + wlen += r; \ +} while(0); + + if (duration < 0) + STORC('-') + + for (f=fmt ; *f ; f++) { + if (*f != '%') { + STORC(*f) + } else { + int zpad, width = 0, precision = 0; + + f++; + + if ((zpad = *f == '0')) + f++; + + if (isdigit(*f)) { + int nread; + sscanf(f, "%d%n", &width, &nread); + f += nread; + } + + if (*f == '.') { + int nread; + f++; + if (1 == sscanf(f, "%d%n", &precision, &nread)) { + if (precision < 0) { + rrd_set_error("Wrong duration format"); + return -1; + } + f += nread; + } + } + + switch(*f) { + case '%': + STORC('%') + break; + case 'W': + STORPF(weeks) + break; + case 'd': + STORPF(days - trunc(weeks)*7.0) + break; + case 'D': + STORPF(days) + break; + case 'h': + STORPF(hours - trunc(days)*24.0) + break; + case 'H': + STORPF(hours) + break; + case 'm': + STORPF(minutes - trunc(hours)*60.0) + break; + case 'M': + STORPF(minutes) + break; + case 's': + STORPF(seconds - trunc(minutes)*60.0) + break; + case 'S': + STORPF(seconds) + break; + case 'f': + STORPF(fabs(duration) - trunc(seconds)*1000.0) + break; + default: + rrd_set_error("Wrong duration format"); + return -1; + } + } + } + + STORC('\0') + if (destlen > 0) + *(dbound-1) = '\0'; + + return wlen-1; + +#undef STORC +#undef STORPF +} + static int timestamp_to_tm(struct tm *tm, double timestamp) { time_t ts; @@ -1786,6 +1911,24 @@ int print_calc( } } break; + case VALUE_FORMATTER_DURATION: + if (!isfinite(printval)) { + prline.u_str = sprintf_alloc("%f", printval); + } else { + const char *fmt; + if (im->gdes[i].format == NULL || im->gdes[i].format[0] == '\0') + fmt = default_duration_fmt; + else + fmt = im->gdes[i].format; + prline.u_str = (char*) malloc(FMT_LEG_LEN*sizeof(char)); + if (!prline.u_str) + return -1; + if (0 > strfduration(prline.u_str, FMT_LEG_LEN, fmt, printval)) { + free(prline.u_str); + return -1; + } + } + break; default: rrd_set_error("Unsupported print value formatter"); return -1; @@ -1830,6 +1973,19 @@ int print_calc( return -1; } break; + case VALUE_FORMATTER_DURATION: + if (!isfinite(printval)) { + snprintf(im->gdes[i].legend, FMT_LEG_LEN, "%f", printval); + } else { + const char *fmt; + if (im->gdes[i].format == NULL || im->gdes[i].format[0] == '\0') + fmt = default_duration_fmt; + else + fmt = im->gdes[i].format; + if (0 > strfduration(im->gdes[i].legend, FMT_LEG_LEN, fmt, printval)) + return -1; + } + break; default: rrd_set_error("Unsupported gprint value formatter"); return -1; @@ -2298,6 +2454,17 @@ int draw_horizontal_grid( graph_label[0] = '\0'; } break; + case VALUE_FORMATTER_DURATION: + { + const char *yfmt; + if (im->primary_axis_format == NULL || im->primary_axis_format[0] == '\0') + yfmt = default_duration_fmt; + else + yfmt = im->primary_axis_format; + if (0 > strfduration(graph_label, sizeof graph_label, yfmt, im->ygrid_scale.gridstep*i)) + graph_label[0] = '\0'; + } + break; default: rrd_set_error("Unsupported left axis value formatter"); return -1; @@ -2340,6 +2507,17 @@ int draw_horizontal_grid( graph_label_right[0] = '\0'; } break; + case VALUE_FORMATTER_DURATION: + { + const char *yfmt; + if (im->second_axis_format == NULL || im->second_axis_format[0] == '\0') + yfmt = default_duration_fmt; + else + yfmt = im->second_axis_format; + if (0 > strfduration(graph_label_right, sizeof graph_label_right, yfmt, sval)) + graph_label_right[0] = '\0'; + } + break; default: rrd_set_error("Unsupported right axis value formatter"); return -1; @@ -4924,6 +5102,8 @@ void rrd_graph_options( im->primary_axis_formatter = VALUE_FORMATTER_NUMERIC; } else if (!strcmp(optarg, "timestamp")) { im->primary_axis_formatter = VALUE_FORMATTER_TIMESTAMP; + } else if (!strcmp(optarg, "duration")) { + im->primary_axis_formatter = VALUE_FORMATTER_DURATION; } else { rrd_set_error("Unknown left axis formatter"); return; @@ -4934,6 +5114,8 @@ void rrd_graph_options( im->second_axis_formatter = VALUE_FORMATTER_NUMERIC; } else if (!strcmp(optarg, "timestamp")) { im->second_axis_formatter = VALUE_FORMATTER_TIMESTAMP; + } else if (!strcmp(optarg, "duration")) { + im->second_axis_formatter = VALUE_FORMATTER_DURATION; } else { rrd_set_error("Unknown right axis formatter"); return; @@ -5196,6 +5378,7 @@ void rrd_graph_options( return; break; case VALUE_FORMATTER_TIMESTAMP: + case VALUE_FORMATTER_DURATION: break; default: rrd_set_error("Unchecked left axis formatter"); @@ -5210,6 +5393,7 @@ void rrd_graph_options( return; break; case VALUE_FORMATTER_TIMESTAMP: + case VALUE_FORMATTER_DURATION: break; default: rrd_set_error("Unchecked right axis formatter"); diff --git a/src/rrd_graph.h b/src/rrd_graph.h index faf629b3..a8e03550 100644 --- a/src/rrd_graph.h +++ b/src/rrd_graph.h @@ -194,6 +194,7 @@ typedef struct ylab_t { enum value_formatter_en { VALUE_FORMATTER_NUMERIC, /* printf */ VALUE_FORMATTER_TIMESTAMP, /* strftime */ + VALUE_FORMATTER_DURATION, /* strfduration */ }; /* this structure describes the elements which can make up a graph. diff --git a/src/rrd_graph_helper.c b/src/rrd_graph_helper.c index 566cdbfa..47219c42 100644 --- a/src/rrd_graph_helper.c +++ b/src/rrd_graph_helper.c @@ -256,6 +256,9 @@ int parseArguments(const char* origarg, parsedargs_t* pa) { } else if ((poscnt>0)&&(strcmp(field,"valstrftime")==0)) { key="vformatter"; value="timestamp"; + } else if ((poscnt>0)&&(strcmp(field,"valstrfduration")==0)) { + key="vformatter"; + value="duration"; } else if ((poscnt>0)&&(strcmp(field,"skipscale")==0)) { key="skipscale"; value="1"; @@ -480,6 +483,8 @@ static graph_desc_t* newGraphDescription(image_desc_t *const im,enum gf_en gf,pa if (frmtr != NULL) { if (strcmp(frmtr,"timestamp") == 0) { gdp->vformatter = VALUE_FORMATTER_TIMESTAMP; + } else if (strcmp(frmtr,"duration") == 0) { + gdp->vformatter = VALUE_FORMATTER_DURATION; } else { rrd_set_error("Unsupported vformatter: %s", frmtr); return NULL; diff --git a/tests/vformatter1 b/tests/vformatter1 index 587dfdf2..2cf54e7c 100755 --- a/tests/vformatter1 +++ b/tests/vformatter1 @@ -24,6 +24,8 @@ rtest "No data, sampling timestamp" '0x0\n---------- --:--:--' "${graphargs[@]}" rtest "No data, value timestamp" '0x0\n-nan' "${graphargs[@]}" 'PRINT:v:%F %T:valstrftime' +rtest "No data, value duration" '0x0\n-nan' "${graphargs[@]}" 'PRINT:v::valstrfduration' + $RRDTOOL update vfmt1.rrd --template v -- 1420070460:0 || exit 1 @@ -33,6 +35,8 @@ rtest "Zero, sampling timestamp" '0x0\n2015-01-01 00:01:00' "${graphargs[@]}" 'P rtest "Zero, value timestamp" '0x0\n1970-01-01 00:00:00' "${graphargs[@]}" 'PRINT:v:%F %T:valstrftime' +rtest "Zero, value duration" '0x0\n0_00_00_000' "${graphargs[@]}" 'PRINT:v:%H_%02m_%02s_%03f:valstrfduration' + $RRDTOOL update vfmt1.rrd --template v -- 1420070520:3000 || exit 1 rtest "3000, numeric" '0x0\n3000.0' "${graphargs[@]}" 'PRINT:v:%0.1lf' @@ -41,3 +45,5 @@ rtest "3000, sampling timestamp" '0x0\n2015-01-01 00:02:00' "${graphargs[@]}" 'P rtest "3000, value timestamp" '0x0\n1970-01-01 00:50:00' "${graphargs[@]}" 'PRINT:v:%F %T:valstrftime' +rtest "3000, value duration" '0x0\n0_00_03_000' "${graphargs[@]}" 'PRINT:v:%H_%02m_%02s_%03f:valstrfduration' + -- 2.47.2