]> git.ipfire.org Git - thirdparty/rrdtool-1.x.git/commitdiff
rrd_graph: Add human scale durations pretty-print 595/head
authorEtienne Buira <etienne.buira@gmail.com>
Mon, 16 Mar 2015 21:23:55 +0000 (22:23 +0100)
committerEtienne Buira <etienne.buira@gmail.com>
Mon, 16 Mar 2015 21:23:55 +0000 (22:23 +0100)
doc/rrdgraph.pod
doc/rrdgraph_graph.pod
src/rrd_graph.c
src/rrd_graph.h
src/rrd_graph_helper.c
tests/vformatter1

index 0e7802a95ac037cd75a2fff81f3c3886dc656c4e..a675a6cd61dda6dfc033350b741ac5d2470a2f6b 100644 (file)
@@ -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<duration>
+
+Values are interpreted as duration in milliseconds. Formatting follows the rules
+of valstrfduration qualified PRINT/GPRINT. See L<rrdgraph_graph>.
+
 =back
 
 [B<--left-axis-format> I<format-string>]
@@ -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<duration>
+
+Values are interpreted as duration in milliseconds. Formatting follows the rules
+of valstrfduration qualified PRINT/GPRINT. See L<rrdgraph_graph>.
+
 =back
 
 
index 183ee39c559fe8401b726d2ca4f2b97ec30f3e99..eb0863d3062a61b45e97402e17ff0ebf8b32b4d3 100644 (file)
@@ -6,7 +6,7 @@ rrdgraph_graph - rrdtool graph command reference
 
 =head1 SYNOPSIS
 
-B<PRINT>B<:>I<vname>B<:>I<format>[B<:strftime>|B<:valstrftime>]
+B<PRINT>B<:>I<vname>B<:>I<format>[B<:strftime>|B<:valstrftime>|B<:valstrfduration>]
 
 B<GPRINT>B<:>I<vname>B<:>I<format>
 
@@ -40,9 +40,9 @@ Similarly, no report is generated if you don't use print options.
 
 =head2 PRINT
 
-=head3 B<PRINT:>I<vname>B<:>I<format>[B<:strftime>|B<:valstrftime>]
+=head3 B<PRINT:>I<vname>B<:>I<format>[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<VDEF> is printed using I<format>. It is
 an error to specify a I<vname> generated by a B<DEF> or B<CDEF>.
 
@@ -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<W>
+
+Number of weeks
+
+=item B<d>
+
+Number of days, modulus number of weeks
+
+=item B<D>
+
+Number of days
+
+=item B<h>
+
+Number of hours, modulus number of days
+
+=item B<H>
+
+Number of hours
+
+=item B<m>
+
+Number of minutes, modulus number of hours
+
+=item B<M>
+
+Number of minutes
+
+=item B<s>
+
+Number of seconds, modulus number of minutes
+
+=item B<S>
+
+Number of seconds
+
+=item B<f>
+
+Number of milliseconds, modulus seconds
+
+=back
+
+
 =head3 B<PRINT:>I<vname>B<:>I<CF>B<:>I<format>
 
 I<Deprecated. Use the new form of this command in new scripts.>
index eaddea0de679925c7dd405a858f8f8577b417dfb..95130cb188eadfeac8e4b6a0e46696c24fd9c649 100644 (file)
@@ -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");
index faf629b3a06022a533d00a9c5ed654efa0dc8cc9..a8e03550fe8b33b9be595b7c20f3a289d36959e7 100644 (file)
@@ -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.
index 566cdbfa2d0fa7d0f0a6488bae8c81c039db0b33..47219c4273822638566207432343b16e79a4cc94 100644 (file)
@@ -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;
index 587dfdf2cbd58d3ff995c3cd587922a87cb8b083..2cf54e7cc1fd5487cd4fdcf30a7769106e42fe72 100755 (executable)
@@ -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'
+