]> git.ipfire.org Git - thirdparty/rrdtool-1.x.git/commitdiff
rrd_graph: Add possibility to graph timestamp data
authorEtienne Buira <etienne.buira@gmail.com>
Mon, 16 Mar 2015 20:55:12 +0000 (21:55 +0100)
committerEtienne Buira <etienne.buira@gmail.com>
Mon, 16 Mar 2015 21:19:18 +0000 (22:19 +0100)
RRA records hold both a value and a timestamp, this patch
permits to represent the _value_ (g?print/axis) as timestamps.

doc/rrdgraph.pod
doc/rrdgraph_graph.pod
src/rrd_graph.c
src/rrd_graph.h
src/rrd_graph_helper.c
tests/Makefile.am
tests/Makefile.in
tests/vformatter1 [new file with mode: 0755]

index 362ae663e5aed25ead7630d98567c116878026a3..0e7802a95ac037cd75a2fff81f3c3886dc656c4e 100644 (file)
@@ -213,11 +213,30 @@ If you have set --y-grid to 'none' not only the labels get suppressed, also
 the space reserved for the labels is removed. You can still add space
 manually if you use the --units-length command to explicitly reserve space.
 
+[B<--left-axis-formatter> I<formatter-name>]
+
+Specify what formater to use to render axis values.
+
+=over
+
+=item B<numeric>
+
+The default, values are expressed as numeric quantities.
+
+=item B<timestamp>
+
+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>.
+
+=back
+
 [B<--left-axis-format> I<format-string>]
 
 By default the format of the axis labels gets determined automatically. If
 you want to do this your self, use this option with the same %lf arguments
-you know from the PRINT and GPRINT commands.
+you know from the PRINT and GPRINT commands, or others if using different
+formatter.
 
 [B<-Y>|B<--alt-y-grid>]
 
@@ -270,11 +289,31 @@ A second axis will be drawn to the right of the graph. It is tied to the
 left axis via the scale and shift parameters. You can also define a label
 for the right axis.
 
+[B<--right-axis-formatter> I<formatter-name>]
+
+Specify what formater to use to render axis values.
+
+=over
+
+=item B<numeric>
+
+The default, values are expressed as numeric quantities.
+
+=item B<timestamp>
+
+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>.
+
+=back
+
+
 [B<--right-axis-format> I<format-string>]
 
 By default the format of the axis labels gets determined automatically. If
 you want to do this your self, use this option with the same %lf arguments
-you know from the PRINT and GPRINT commands.
+you know from the PRINT and GPRINT commands, or others if using different
+formatter.
 
 =head2 Legend
 
index 1801a7136af93f41a8681c6948e355e74eef1304..183ee39c559fe8401b726d2ca4f2b97ec30f3e99 100644 (file)
@@ -6,7 +6,7 @@ rrdgraph_graph - rrdtool graph command reference
 
 =head1 SYNOPSIS
 
-B<PRINT>B<:>I<vname>B<:>I<format>
+B<PRINT>B<:>I<vname>B<:>I<format>[B<:strftime>|B<:valstrftime>]
 
 B<GPRINT>B<:>I<vname>B<:>I<format>
 
@@ -40,11 +40,11 @@ Similarly, no report is generated if you don't use print options.
 
 =head2 PRINT
 
-=head3 B<PRINT:>I<vname>B<:>I<format>[B<:strftime>]
+=head3 B<PRINT:>I<vname>B<:>I<format>[B<:strftime>|B<:valstrftime>]
 
-Depending on the context, either the value component or the time
-component 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>.
+Depending on the context, either the value component (no suffix or valstrftime)
+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>.
 
 Any text in I<format> is printed literally with one exception:
 The percent character introduces a formatter string. This string
@@ -87,7 +87,8 @@ will all use the same magnitude unit except for zero values.
 If you PRINT a VDEF value, you can also print the time associated with it by appending the string
 B<:strftime> to the format. Note that RRDtool uses the strftime function of your OSs C library. This means that
 the conversion specifier may vary. Check the manual page if you are uncertain. The following is a list of
-conversion specifiers usually supported across the board. 
+conversion specifiers usually supported across the board. Formatting values
+interpreted as timestamps with B<:valstrftime> is done likewise.
 
 =over
 
index 0ae82977630d5d936ddf78eb0fb0580d714870de..eaddea0de679925c7dd405a858f8f8577b417dfb 100644 (file)
@@ -224,6 +224,8 @@ gfx_color_t graph_col[] =   /* default colors */
     {0.00, 0.00, 0.00, 1.00}    /* frame      */
 };
 
+const char default_timestamp_fmt[] = "%Y-%m-%d %H:%M:%S";
+
 
 /* #define DEBUG */
 
@@ -1631,6 +1633,23 @@ time_t find_next_time(
 
 }
 
+static int timestamp_to_tm(struct tm *tm, double timestamp)
+{
+    time_t ts;
+
+    if (timestamp < LLONG_MIN || timestamp > LLONG_MAX)
+        return 1;
+
+    ts = (long long int) timestamp;
+
+    if (ts != (long long int) timestamp)
+        return 1;
+
+    gmtime_r(&ts, tm);
+
+    return 0;
+}
+
 
 /* calculate values required for PRINT and GPRINT functions */
 
@@ -1707,22 +1726,24 @@ int print_calc(
                 }
             }           /* prepare printval */
 
-            if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
-                /* Magfact is set to -1 upon entry to print_calc.  If it
-                 * is still less than 0, then we need to run auto_scale.
-                 * Otherwise, put the value into the correct units.  If
-                 * the value is 0, then do not set the symbol or magnification
-                 * so next the calculation will be performed again. */
-                if (magfact < 0.0) {
+            if (!im->gdes[i].strftm && im->gdes[i].vformatter == VALUE_FORMATTER_NUMERIC) {
+                if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
+                    /* Magfact is set to -1 upon entry to print_calc.  If it
+                     * is still less than 0, then we need to run auto_scale.
+                     * Otherwise, put the value into the correct units.  If
+                     * the value is 0, then do not set the symbol or magnification
+                     * so next the calculation will be performed again. */
+                    if (magfact < 0.0) {
+                        auto_scale(im, &printval, &si_symb, &magfact);
+                        if (printval == 0.0)
+                            magfact = -1.0;
+                    } else {
+                        printval /= magfact;
+                    }
+                    *(++percent_s) = 's';
+                } else if (strstr(im->gdes[i].format, "%s") != NULL) {
                     auto_scale(im, &printval, &si_symb, &magfact);
-                    if (printval == 0.0)
-                        magfact = -1.0;
-                } else {
-                    printval /= magfact;
                 }
-                *(++percent_s) = 's';
-            } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
-                auto_scale(im, &printval, &si_symb, &magfact);
             }
 
             if (im->gdes[i].gf == GF_PRINT) {
@@ -1736,12 +1757,41 @@ int print_calc(
                         strftime(prline.u_str,
                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
                     }
-                } else if (bad_format_print(im->gdes[i].format)) {
-                    return -1;
                 } else {
-                    prline.u_str =
-                        sprintf_alloc(im->gdes[i].format, printval, si_symb);
+                    struct tm tmval;
+                    switch(im->gdes[i].vformatter) {
+                    case VALUE_FORMATTER_NUMERIC:
+                        if (bad_format_print(im->gdes[i].format)) {
+                            return -1;
+                        } else {
+                            prline.u_str =
+                                sprintf_alloc(im->gdes[i].format, printval, si_symb);
+                        }
+                        break;
+                    case VALUE_FORMATTER_TIMESTAMP:
+                        if (!isfinite(printval) || timestamp_to_tm(&tmval, printval)) {
+                            prline.u_str = sprintf_alloc("%.0f", printval);
+                        } else {
+                            const char *fmt;
+                            if (im->gdes[i].format == NULL || im->gdes[i].format[0] == '\0')
+                                fmt = default_timestamp_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 == strftime(prline.u_str, FMT_LEG_LEN, fmt, &tmval)) {
+                                free(prline.u_str);
+                                return -1;
+                            }
+                        }
+                        break;
+                    default:
+                        rrd_set_error("Unsupported print value formatter");
+                        return -1;
+                    }
                 }
+
                 grinfo_push(im,
                             sprintf_alloc
                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
@@ -1757,12 +1807,33 @@ int print_calc(
                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
                     }
                 } else {
-                    if (bad_format_print(im->gdes[i].format)) {
-                       return -1;
+                    struct tm tmval;
+                    switch(im->gdes[i].vformatter) {
+                    case VALUE_FORMATTER_NUMERIC:
+                        if (bad_format_print(im->gdes[i].format)) {
+                           return -1;
+                        }
+                        snprintf(im->gdes[i].legend,
+                                 FMT_LEG_LEN - 2,
+                                 im->gdes[i].format, printval, si_symb);
+                        break;
+                    case VALUE_FORMATTER_TIMESTAMP:
+                        if (!isfinite(printval) || timestamp_to_tm(&tmval, printval)) {
+                            snprintf(im->gdes[i].legend, FMT_LEG_LEN, "%.0f", printval);
+                        } else {
+                            const char *fmt;
+                            if (im->gdes[i].format == NULL || im->gdes[i].format[0] == '\0')
+                                fmt = default_timestamp_fmt;
+                            else
+                                fmt = im->gdes[i].format;
+                            if (0 == strftime(im->gdes[i].legend, FMT_LEG_LEN, fmt, &tmval))
+                                return -1;
+                        }
+                        break;
+                    default:
+                        rrd_set_error("Unsupported gprint value formatter");
+                        return -1;
                     }
-                    snprintf(im->gdes[i].legend,
-                             FMT_LEG_LEN - 2,
-                             im->gdes[i].format, printval, si_symb);
                 }
                 graphelement = 1;
             }
@@ -2168,65 +2239,110 @@ int draw_horizontal_grid(
             if (i % im->ygrid_scale.labfact == 0
                 || (nlabels == 1
                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
-                if (im->symbol == ' ') {
-                    if (im->primary_axis_format == NULL || im->primary_axis_format[0] == '\0') {
-                        if (im->extra_flags & ALTYGRID) {
-                            snprintf(graph_label, sizeof graph_label,
-                                    im->ygrid_scale.labfmt,
-                                    scaledstep * (double) i);
-                        } else {
-                            if (MaxY < 10) {
-                                snprintf(graph_label, sizeof graph_label, "%4.1f",
+                switch(im->primary_axis_formatter) {
+                case VALUE_FORMATTER_NUMERIC:
+                    if (im->symbol == ' ') {
+                        if (im->primary_axis_format == NULL || im->primary_axis_format[0] == '\0') {
+                            if (im->extra_flags & ALTYGRID) {
+                                snprintf(graph_label, sizeof graph_label,
+                                        im->ygrid_scale.labfmt,
                                         scaledstep * (double) i);
                             } else {
-                                snprintf(graph_label, sizeof graph_label,"%4.0f",
-                                        scaledstep * (double) i);
+                                if (MaxY < 10) {
+                                    snprintf(graph_label, sizeof graph_label, "%4.1f",
+                                            scaledstep * (double) i);
+                                } else {
+                                    snprintf(graph_label, sizeof graph_label,"%4.0f",
+                                            scaledstep * (double) i);
+                                }
                             }
+                        } else {
+                            snprintf(graph_label, sizeof graph_label, im->primary_axis_format,
+                                    scaledstep * (double) i);
                         }
                     } else {
-                        snprintf(graph_label, sizeof graph_label, im->primary_axis_format,
-                                scaledstep * (double) i);
-                    }
-                } else {
-                    char      sisym = (i == 0 ? ' ' : im->symbol);
-                    if (im->primary_axis_format == NULL || im->primary_axis_format[0] == '\0') {
-                        if (im->extra_flags & ALTYGRID) {
-                            snprintf(graph_label,sizeof graph_label,
-                                    im->ygrid_scale.labfmt,
-                                    scaledstep * (double) i, sisym);
-                        } else {
-                            if (MaxY < 10) {
-                                snprintf(graph_label, sizeof graph_label,"%4.1f %c",
+                        char      sisym = (i == 0 ? ' ' : im->symbol);
+                        if (im->primary_axis_format == NULL || im->primary_axis_format[0] == '\0') {
+                            if (im->extra_flags & ALTYGRID) {
+                                snprintf(graph_label,sizeof graph_label,
+                                        im->ygrid_scale.labfmt,
                                         scaledstep * (double) i, sisym);
                             } else {
-                                snprintf(graph_label, sizeof graph_label, "%4.0f %c",
-                                        scaledstep * (double) i, sisym);
+                                if (MaxY < 10) {
+                                    snprintf(graph_label, sizeof graph_label,"%4.1f %c",
+                                            scaledstep * (double) i, sisym);
+                                } else {
+                                    snprintf(graph_label, sizeof graph_label, "%4.0f %c",
+                                            scaledstep * (double) i, sisym);
+                                }
                             }
+                        } else {
+                            sprintf(graph_label, im->primary_axis_format,
+                                    scaledstep * (double) i, sisym);
                         }
-                    } else {
-                        sprintf(graph_label, im->primary_axis_format,
-                                scaledstep * (double) i, sisym);
                     }
+                    break;
+                case VALUE_FORMATTER_TIMESTAMP:
+                    {
+                        struct tm tm;
+                        const char *yfmt;
+                        if (im->primary_axis_format == NULL || im->primary_axis_format[0] == '\0')
+                            yfmt = default_timestamp_fmt;
+                        else
+                            yfmt = im->primary_axis_format;
+                        if (timestamp_to_tm(&tm, im->ygrid_scale.gridstep*i))
+                            snprintf(graph_label, sizeof graph_label, "%f",
+                                     im->ygrid_scale.gridstep * i);
+                        else
+                            if (0 == strftime(graph_label, sizeof graph_label, yfmt, &tm))
+                                graph_label[0] = '\0';
+                    }
+                    break;
+                default:
+                    rrd_set_error("Unsupported left axis value formatter");
+                    return -1;
                 }
                 nlabels++;
                 if (im->second_axis_scale != 0){
                         char graph_label_right[100];
                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
-                        if (im->second_axis_format == NULL || im->second_axis_format[0] == '\0') {
-                            if (!second_axis_magfact){
-                                double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
-                                auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
-                            }
-                            sval /= second_axis_magfact;
+                        switch(im->second_axis_formatter) {
+                        case VALUE_FORMATTER_NUMERIC:
+                            if (im->second_axis_format == NULL || im->second_axis_format[0] == '\0') {
+                                if (!second_axis_magfact){
+                                    double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
+                                    auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
+                                }
+                                sval /= second_axis_magfact;
 
-                            if(MaxY < 10) {
-                                snprintf(graph_label_right, sizeof graph_label_right, "%5.1f %s",sval,second_axis_symb);
-                            } else {
-                                snprintf(graph_label_right, sizeof graph_label_right, "%5.0f %s",sval,second_axis_symb);
+                                if(MaxY < 10) {
+                                    snprintf(graph_label_right, sizeof graph_label_right, "%5.1f %s",sval,second_axis_symb);
+                                } else {
+                                    snprintf(graph_label_right, sizeof graph_label_right, "%5.0f %s",sval,second_axis_symb);
+                                }
                             }
-                        }
-                        else {
-                           snprintf(graph_label_right, sizeof graph_label_right, im->second_axis_format,sval,"");
+                            else {
+                               snprintf(graph_label_right, sizeof graph_label_right, im->second_axis_format,sval,"");
+                            }
+                            break;
+                        case VALUE_FORMATTER_TIMESTAMP:
+                            {
+                                struct tm tm;
+                                const char *yfmt;
+                                if (im->second_axis_format == NULL || im->second_axis_format[0] == '\0')
+                                    yfmt = default_timestamp_fmt;
+                                else
+                                    yfmt = im->second_axis_format;
+                                if (timestamp_to_tm(&tm, sval))
+                                    snprintf(graph_label_right, sizeof graph_label_right, "%f", sval);
+                                else
+                                    if (0 == strftime(graph_label_right, sizeof graph_label, yfmt, &tm))
+                                        graph_label_right[0] = '\0';
+                             }
+                             break;
+                        default:
+                            rrd_set_error("Unsupported right axis value formatter");
+                            return -1;
                         }
                         gfx_text ( im,
                                X1+7, Y0,
@@ -2770,7 +2886,7 @@ void axis_paint(
 
 }
 
-void grid_paint(
+int grid_paint(
     image_desc_t *im)
 {
     long      i;
@@ -2802,6 +2918,8 @@ void grid_paint(
             res = horizontal_log_grid(im);
         } else {
             res = draw_horizontal_grid(im);
+            if (res < 0)
+                return -1;
         }
 
         /* dont draw horizontal grid if there is no min and max val */
@@ -3026,6 +3144,7 @@ void grid_paint(
             }
         }
     }
+    return 0;
 }
 
 
@@ -3898,7 +4017,8 @@ int graph_paint_timestring(
 
     /* grid_paint also does the text */
     if (!(im->extra_flags & ONLY_GRAPH))
-        grid_paint(im);
+        if (grid_paint(im))
+            return -1;
     if (!(im->extra_flags & ONLY_GRAPH))
         axis_paint(im);
     /* the RULES are the last thing to paint ... */
@@ -4128,6 +4248,7 @@ int gdes_alloc(
     im->gdes[im->gdes_c - 1].legend[0] = '\0';
     im->gdes[im->gdes_c - 1].format[0] = '\0';
     im->gdes[im->gdes_c - 1].strftm = 0;
+    im->gdes[im->gdes_c - 1].vformatter = VALUE_FORMATTER_NUMERIC;
     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
     im->gdes[im->gdes_c - 1].ds = -1;
     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
@@ -4426,7 +4547,9 @@ void rrd_graph_init(
     im->second_axis_shift = 0; /* no shift by default */
     im->second_axis_legend = NULL;
     im->second_axis_format = NULL;
+    im->second_axis_formatter = VALUE_FORMATTER_NUMERIC;
     im->primary_axis_format = NULL;
+    im->primary_axis_formatter = VALUE_FORMATTER_NUMERIC;
     im->yorigin = 0;
     im->yOriginLegend = 0;
     im->yOriginLegendY = 0;
@@ -4555,6 +4678,8 @@ void rrd_graph_options(
         { "week-fmt",           required_argument, 0, 1010},
         { "graph-type",         required_argument, 0, 1011},
         { "left-axis-format",   required_argument, 0, 1012},
+        { "left-axis-formatter",required_argument, 0, 1013},
+        { "right-axis-formatter",required_argument,0, 1014},
         {  0, 0, 0, 0}
 };
 /* *INDENT-ON* */
@@ -4781,9 +4906,6 @@ void rrd_graph_options(
             }
             break;
         case 1004:
-            if (bad_format_axis(optarg)){
-                return;
-            }
             im->second_axis_format=strdup(optarg);
             if (!im->second_axis_format) {
                 rrd_set_error("cannot allocate memory for second_axis_format");
@@ -4791,15 +4913,32 @@ void rrd_graph_options(
             }
             break;
         case 1012:
-            if (bad_format_axis(optarg)){
-                return;
-            }
             im->primary_axis_format=strdup(optarg);
             if (!im->primary_axis_format) {
                 rrd_set_error("cannot allocate memory for primary_axis_format");
                 return;
             }
             break;
+        case 1013:
+            if (!strcmp(optarg, "numeric")) {
+                im->primary_axis_formatter = VALUE_FORMATTER_NUMERIC;
+            } else if (!strcmp(optarg, "timestamp")) {
+                im->primary_axis_formatter = VALUE_FORMATTER_TIMESTAMP;
+            } else {
+                rrd_set_error("Unknown left axis formatter");
+                return;
+            }
+            break;
+        case 1014:
+            if (!strcmp(optarg, "numeric")) {
+                im->second_axis_formatter = VALUE_FORMATTER_NUMERIC;
+            } else if (!strcmp(optarg, "timestamp")) {
+                im->second_axis_formatter = VALUE_FORMATTER_TIMESTAMP;
+            } else {
+                rrd_set_error("Unknown right axis formatter");
+                return;
+            }
+            break;
         case 'v':
             im->ylegend=strdup(optarg);
             if (!im->ylegend) {
@@ -5050,6 +5189,33 @@ void rrd_graph_options(
     pango_layout_context_changed(im->layout);
 
 
+    if (im->primary_axis_format != NULL && im->primary_axis_format[0] != '\0') {
+        switch(im->primary_axis_formatter) {
+        case VALUE_FORMATTER_NUMERIC:
+            if (bad_format_axis(im->primary_axis_format))
+                return;
+            break;
+        case VALUE_FORMATTER_TIMESTAMP:
+            break;
+        default:
+            rrd_set_error("Unchecked left axis formatter");
+            return;
+        }
+    }
+
+    if (im->second_axis_format != NULL && im->second_axis_format[0] != '\0') {
+        switch(im->second_axis_formatter) {
+        case VALUE_FORMATTER_NUMERIC:
+            if (bad_format_axis(im->second_axis_format))
+                return;
+            break;
+        case VALUE_FORMATTER_TIMESTAMP:
+            break;
+        default:
+            rrd_set_error("Unchecked right axis formatter");
+            return;
+        }
+    }
 
     if (im->logarithmic && im->minval <= 0) {
         rrd_set_error
index 1429d08686849b57a16d69064dad7881f5a0a567..faf629b3a06022a533d00a9c5ed654efa0dc8cc9 100644 (file)
@@ -191,6 +191,11 @@ typedef struct ylab_t {
     int       lfac[4];  /* associated label spacing */
 } ylab_t;
 
+enum value_formatter_en {
+    VALUE_FORMATTER_NUMERIC,    /* printf */
+    VALUE_FORMATTER_TIMESTAMP,  /* strftime */
+};
+
 /* this structure describes the elements which can make up a graph.
    because they are quite diverse, not all elements will use all the
    possible parts of the structure. */
@@ -220,7 +225,8 @@ typedef struct graph_desc_t {
        double    gradheight;
     char      format[FMT_LEG_LEN + 5];  /* format for PRINT AND GPRINT */
     char      legend[FMT_LEG_LEN + 5];  /* legend */
-    int       strftm;   /* should the VDEF legend be formated with strftime */
+    int       strftm;   /* should the VDEF legend be the time component formated with strftime */
+    enum value_formatter_en vformatter; /* what value formatter to use (if !strftm) */
     double    leg_x, leg_y; /* location of legend */
     double    yrule;    /* value for y rule line and for VDEF */
     time_t    xrule;    /* time for x rule line and for VDEF */
@@ -281,7 +287,9 @@ typedef struct image_desc_t {
     double    second_axis_shift; /* how much is it shifted vs the first axis */
     char      *second_axis_legend; /* label to put on the seond axis */
     char      *second_axis_format; /* format for the numbers on the scond axis */
+    enum value_formatter_en second_axis_formatter;  /* How to format axis values */
     char      *primary_axis_format; /* format for the numbers on the primary axis */
+    enum value_formatter_en primary_axis_formatter; /* How to format axis values */
     double    ygridstep;    /* user defined step for y grid */
     int       ylabfact; /* every how many y grid shall a label be written ? */
     double    tabwidth; /* tabwdith */
@@ -415,7 +423,7 @@ void      vertical_grid(
     image_desc_t *);
 void      axis_paint(
     image_desc_t *);
-void      grid_paint(
+int      grid_paint(
     image_desc_t *);
 int       lazy_check(
     image_desc_t *);
index 2cf5f71c9a4534551d40bfacefdf8d47f9534865..566cdbfa2d0fa7d0f0a6488bae8c81c039db0b33 100644 (file)
@@ -253,6 +253,9 @@ int parseArguments(const char* origarg, parsedargs_t* pa) {
        } else if ((poscnt>0)&&(strcmp(field,"strftime")==0)) {
          key="strftime";
          value="1";
+        } else if ((poscnt>0)&&(strcmp(field,"valstrftime")==0)) {
+          key="vformatter";
+          value="timestamp";
        } else if ((poscnt>0)&&(strcmp(field,"skipscale")==0)) {
          key="skipscale";
          value="1";
@@ -362,7 +365,7 @@ int parse_color( const char *const string, struct gfx_color_t *c)
 #define PARSE_DASHES       (PARSE_FIELD1|(1ULL<<20))
 #define PARSE_HEIGHT       (PARSE_FIELD1|(1ULL<<21))
 #define PARSE_FORMAT       (PARSE_FIELD1|(1ULL<<22))
-#define PARSE_STRFTIME     (PARSE_FIELD1|(1ULL<<23))
+#define PARSE_STRFTIMEVFMT (PARSE_FIELD1|(1ULL<<23))
 #define PARSE_FRACTION     (PARSE_FIELD1|(1ULL<<24))
 /* VNAME Special cases for generic parsing */
 #define PARSE_VNAMEDEF            (PARSE_VNAME|(1ULL<<57))
@@ -470,9 +473,18 @@ static graph_desc_t* newGraphDescription(image_desc_t *const im,enum gf_en gf,pa
       dprintfparsed("got format: %s\n",format);
     }
   }
-  if (bitscmp(PARSE_STRFTIME)) {
+  if (bitscmp(PARSE_STRFTIMEVFMT)) {
     char *strft=getKeyValueArgument("strftime",1,pa);
+    char *frmtr=getKeyValueArgument("vformatter",1,pa);
     gdp->strftm=(strft)?1:0;
+    if (frmtr != NULL) {
+      if (strcmp(frmtr,"timestamp") == 0) {
+        gdp->vformatter = VALUE_FORMATTER_TIMESTAMP;
+      } else {
+        rrd_set_error("Unsupported vformatter: %s", frmtr);
+        return NULL;
+      }
+    }
     dprintfparsed("got strftime: %s\n",strft);
   }
   if (bitscmp(PARSE_STACK)) {
@@ -1206,7 +1218,7 @@ int parse_gprint(enum gf_en gf,parsedargs_t*pa,image_desc_t *const im) {
                                        PARSE_VNAMEREF
                                        |PARSE_CF
                                        |PARSE_FORMAT
-                                       |PARSE_STRFTIME
+                                       |PARSE_STRFTIMEVFMT
                                        );
   if (!gdp) { return 1;}
    /* here we parse pos arguments locally */
index dfd245b66cb22543e786369bdcf36ea08bdb70c9..0dc1b1a33a60c939f1035cbf413644782fdd9543 100644 (file)
@@ -4,7 +4,7 @@ TESTS = modify1 modify2 modify3 modify4 modify5 \
        dump-restore \
        create-with-source-1 create-with-source-2 create-with-source-3 \
        create-with-source-4 create-with-source-and-mapping-1 \
-       create-from-template-1 dcounter1
+       create-from-template-1 dcounter1 vformatter1
 EXTRA_DIST = Makefile.am \
        alltests functions \
        modify1 modify-test1.create.dump modify-test1.mod1.dump \
@@ -15,7 +15,7 @@ EXTRA_DIST = Makefile.am \
        rrdcreate \
        tune1 tune1-testa-mod1.dump tune1-testa-mod2.dump tune1-testorg.dump \
        tune2 tune2-testa-mod1.dump tune2-testorg.dump \
-       valgrind-supressions dcounter1 dcounter1.output
+       valgrind-supressions dcounter1 dcounter1.output vformatter1
 # NB: AM_TESTS_ENVIRONMENT not available until automake 1.12
 TESTS_ENVIRONMENT = \
        BASEDIR=${abs_srcdir} ; export BASEDIR ; \
index f04d0f8333215606da67a013f1acb41e2435a044..f20bbe92617d9608343d719d725b51567307bd89 100644 (file)
@@ -530,7 +530,7 @@ TESTS = modify1 modify2 modify3 modify4 modify5 \
        dump-restore \
        create-with-source-1 create-with-source-2 create-with-source-3 \
        create-with-source-4 create-with-source-and-mapping-1 \
-       create-from-template-1 dcounter1
+       create-from-template-1 dcounter1 vformatter1
 
 EXTRA_DIST = Makefile.am \
        alltests functions \
@@ -542,7 +542,7 @@ EXTRA_DIST = Makefile.am \
        rrdcreate \
        tune1 tune1-testa-mod1.dump tune1-testa-mod2.dump tune1-testorg.dump \
        tune2 tune2-testa-mod1.dump tune2-testorg.dump \
-       valgrind-supressions dcounter1 dcounter1.output
+       valgrind-supressions dcounter1 dcounter1.output vformatter1
 
 # NB: AM_TESTS_ENVIRONMENT not available until automake 1.12
 TESTS_ENVIRONMENT = \
@@ -861,6 +861,13 @@ dcounter1.log: dcounter1
        --log-file $$b.log --trs-file $$b.trs \
        $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
        "$$tst" $(AM_TESTS_FD_REDIRECT)
+vformatter1.log: vformatter1
+       @p='vformatter1'; \
+       b='vformatter1'; \
+       $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+       --log-file $$b.log --trs-file $$b.trs \
+       $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+       "$$tst" $(AM_TESTS_FD_REDIRECT)
 .test.log:
        @p='$<'; \
        $(am__set_b); \
diff --git a/tests/vformatter1 b/tests/vformatter1
new file mode 100755 (executable)
index 0000000..587dfdf
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+. $(dirname $0)/functions
+
+export TZ=UTC
+
+function rtest() {
+    testname="$1"
+    xpected="$2"
+    shift 2 || exit 1
+    diff -u <(echo -e "$xpected") <($RRDTOOL "$@")
+    report "$testname"
+    #&& echo "OK: $testname" || echo "FAIL: $testname"
+}
+
+$RRDTOOL create vfmt1.rrd --start 1420070400 --step 60s DS:v:GAUGE:60:U:U RRA:LAST:0:1:10 || exit 1
+
+declare -a graphargs
+graphargs=(graph /dev/null --start 1420070400 --end 1420071000 'DEF:dv=vfmt1.rrd:v:LAST' 'VDEF:v=dv,LAST')
+
+rtest "No data, numeric" '0x0\n-nan' "${graphargs[@]}" 'PRINT:v:%0.1lf'
+
+rtest "No data, sampling timestamp" '0x0\n---------- --:--:--' "${graphargs[@]}" 'PRINT:v:%F %T:strftime'
+
+rtest "No data, value timestamp" '0x0\n-nan' "${graphargs[@]}" 'PRINT:v:%F %T:valstrftime'
+
+
+$RRDTOOL update vfmt1.rrd --template v -- 1420070460:0 || exit 1
+
+rtest "Zero, numeric" '0x0\n0.0' "${graphargs[@]}" 'PRINT:v:%0.1lf'
+
+rtest "Zero, sampling timestamp" '0x0\n2015-01-01 00:01:00' "${graphargs[@]}" 'PRINT:v:%F %T:strftime'
+
+rtest "Zero, value timestamp" '0x0\n1970-01-01 00:00:00' "${graphargs[@]}" 'PRINT:v:%F %T:valstrftime'
+
+$RRDTOOL update vfmt1.rrd --template v -- 1420070520:3000 || exit 1
+
+rtest "3000, numeric" '0x0\n3000.0' "${graphargs[@]}" 'PRINT:v:%0.1lf'
+
+rtest "3000, sampling timestamp" '0x0\n2015-01-01 00:02:00' "${graphargs[@]}" 'PRINT:v:%F %T:strftime'
+
+rtest "3000, value timestamp" '0x0\n1970-01-01 00:50:00' "${graphargs[@]}" 'PRINT:v:%F %T:valstrftime'
+