]> git.ipfire.org Git - thirdparty/rrdtool-1.x.git/commitdiff
rrdcreate: support scale suffix for heartbeat/steps/rows
authorPeter A. Bigot <pab@pabigot.com>
Thu, 15 May 2014 21:27:41 +0000 (16:27 -0500)
committerPeter A. Bigot <pab@pabigot.com>
Thu, 15 May 2014 21:27:41 +0000 (16:27 -0500)
This feature allows this opaque create specification:

  rrdtool create power1.rrd \
    --start 0 --step 1 \
    DS:watts:GAUGE:300:0:24000 \
    RRA:AVERAGE:0.5:1:864000 \
    RRA:AVERAGE:0.5:60:129600 \
    RRA:AVERAGE:0.5:3600:13392 \
    RRA:AVERAGE:0.5:86400:3660

to be expressed with more understandable durations:

  rrdtool create power2.rrd \
    --start 0 --step 1s \
    DS:watts:GAUGE:5m:0:24000 \
    RRA:AVERAGE:0.5:1s:10d \
    RRA:AVERAGE:0.5:1m:90d \
    RRA:AVERAGE:0.5:1h:18M \
    RRA:AVERAGE:0.5:1d:10y

Signed-off-by: Peter A. Bigot <pab@pabigot.com>
doc/rrdcreate.pod
src/rrd_create.c

index d65843c123f5f9d49e876edfda354d553a2a2131..0fe67ccec3355e5cba97af18f8be3b81c64d9e70 100644 (file)
@@ -37,6 +37,8 @@ I<rrdfetch> documentation for other ways to specify time.
 
 Specifies the base interval in seconds with which data will be fed
 into the B<RRD>.
+A scaling factor may be present as a suffix to the integer; see
+L<"STEP, HEARTBEAT, and Rows As Durations">.
 
 =head2 B<--no-overwrite>
 
@@ -220,9 +222,11 @@ to the number of PDPs in the interval. Thus, it ranges from 0 to 1 (exclusive).
 
 I<steps> defines how many of these I<primary data points> are used to build
 a I<consolidated data point> which then goes into the archive.
+See also L<"STEP, HEARTBEAT, and Rows As Durations">.
 
 I<rows> defines how many generations of data values are kept in an B<RRA>.
 Obviously, this has to be greater than zero.
+See also L<"STEP, HEARTBEAT, and Rows As Durations">.
 
 =head1 Aberrant Behavior Detection with Holt-Winters Forecasting
 
@@ -398,6 +402,79 @@ The time interval this window represents depends on the interval between
 primary data points. If the FAILURES B<RRA> is implicitly created, the
 default value is 9.
 
+=head1 STEP, HEARTBEAT, and Rows As Durations
+
+Traditionally RRDtool specified PDP intervals in seconds, and most
+other values as either seconds or PDP counts.  This made the
+specification for databases rather opaque; for example
+
+ rrdtool create power.rrd \
+   --start now-2h --step 1 \
+   DS:watts:GAUGE:300:0:24000 \
+   RRA:AVERAGE:0.5:1:864000 \
+   RRA:AVERAGE:0.5:60:129600 \
+   RRA:AVERAGE:0.5:3600:13392 \
+   RRA:AVERAGE:0.5:86400:3660
+
+creates a database of power values collected once per second, with a
+five minute (300 second) heartbeat, and four B<RRA>s: ten days of one
+second, 90 days of one minute, 18 months of one hour, and ten years of
+one day averages.
+
+Step, heartbeat, and PDP counts and rows may also be specified as
+durations, which are positive integers with a single-character suffix
+that specifies a scaling factor:
+
+=over
+
+=item C<s>
+
+indicates seconds
+
+=item C<m>
+
+indicates minutes.  The value is multipled by 60.
+
+=item C<h>
+
+indicates hours.  The value is multipled by 3600 (or C<60m>).
+
+=item C<d>
+
+indicates days.  The value is multipled by 86400 (or C<24h>).
+
+=item C<w>
+
+indicates weeks.  The value is multipled by 604800 (or C<7d>).
+
+=item C<M>
+
+indicates months.  The value is multipled by 2678400 (or C<31d>).
+(Note this factor accommodates the maximum number of days in a month.)
+
+=item C<y>
+
+indicates years.  The value is multipled by 31622400 (or C<366d>).
+(Note this factor accommodates leap years.)
+
+=back
+
+Scaled step and heartbeat values (which are natively durations in
+seconds) are used directly, while consolidation function row arguments
+are divided by their step to produce the number of rows.  Note that
+the multiplication factors for months and years correspond to the
+maximum number of days in a month or year.
+
+With this feature the same specification as above can be written as:
+
+ rrdtool create power.rrd \
+   --start now-2h --step 1s \
+   DS:watts:GAUGE:5m:0:24000 \
+   RRA:AVERAGE:0.5:1s:10d \
+   RRA:AVERAGE:0.5:1m:90d \
+   RRA:AVERAGE:0.5:1h:18M \
+   RRA:AVERAGE:0.5:1d:10y
+
 =head1 The HEARTBEAT and the STEP
 
 Here is an explanation by Don Baarda on the inner workings of RRDtool.
index b00b0bd8048299c47b5c0e986e8f0aa061993cd5..4824dbb2c674504964a6031a5a519812a75ec048 100644 (file)
@@ -28,6 +28,43 @@ void      parseGENERIC_DS(
     const char *def,
     ds_def_t *ds_def);
 
+static int convert_to_count (const char * token,
+                            unsigned long * valuep,
+                            unsigned long divisor)
+{
+  char * ep = NULL;
+  unsigned long int value = strtoul(token, &ep, 10);
+  switch (*ep) {
+    case 0: /* count, no conversion */
+      break;
+    case 's': /* seconds */
+      value /= divisor;
+      break;
+    case 'm': /* minutes */
+      value = (60 * value) / divisor;
+      break;
+    case 'h': /* hours */
+      value = (60 * 60 * value) / divisor;
+      break;
+    case 'd': /* days */
+      value = (24 * 60 * 60 * value) / divisor;
+      break;
+    case 'w': /* weeks */
+      value = (7 * 24 * 60 * 60 * value) / divisor;
+      break;
+    case 'M': /* months */
+      value = (31 * 24 * 60 * 60 * value) / divisor;
+      break;
+    case 'y': /* years */
+      value = (366 * 24 * 60 * 60 * value) / divisor;
+      break;
+    default:
+      return 0;
+  }
+  *valuep = value;
+  return (0 != value);
+}
+
 int rrd_create(
     int argc,
     char **argv)
@@ -45,7 +82,6 @@ int rrd_create(
     unsigned long pdp_step = 300;
     rrd_time_value_t last_up_tv;
     char     *parsetime_error = NULL;
-    long      long_tmp;
     int       rc;
     char * opt_daemon = NULL;
     int       opt_no_overwrite = 0;
@@ -93,12 +129,10 @@ int rrd_create(
             break;
 
         case 's':
-            long_tmp = atol(optarg);
-            if (long_tmp < 1) {
+            if (! convert_to_count(optarg, &pdp_step, 1)) {
                 rrd_set_error("step size should be no less than one second");
                 return (-1);
             }
-            pdp_step = long_tmp;
             break;
 
         case 'O':
@@ -193,7 +227,6 @@ int parseRRA(const char *def,
     unsigned short token_idx, error_flag, period = 0;
     int       cf_id = -1;
     int       token_min = 4;
-    int       row_cnt;
     char     *require_version = NULL;
 
     memset(rra_def, 0, sizeof(rra_def_t));
@@ -264,10 +297,8 @@ int parseRRA(const char *def,
            case CF_SEASONAL:
            case CF_DEVPREDICT:
            case CF_FAILURES:
-               row_cnt = atoi(token);
-               if (row_cnt <= 0)
-                   rrd_set_error("Invalid row count: %i", row_cnt);
-               rra_def->row_cnt = row_cnt;
+               if (! convert_to_count(token, &rra_def->row_cnt, 1))
+                   rrd_set_error("Invalid row count: %s", token);
                break;
            default:
                rra_def->par[RRA_cdp_xff_val].u_val = atof(token);
@@ -314,8 +345,7 @@ int parseRRA(const char *def,
                    atoi(token) - 1;
                break;
            default:
-               rra_def->pdp_cnt = atoi(token);
-               if (atoi(token) < 1)
+               if (! convert_to_count(token, &rra_def->pdp_cnt, rrd->stat_head->pdp_step))
                    rrd_set_error("Invalid step: must be >= 1");
                break;
            }
@@ -357,16 +387,14 @@ int parseRRA(const char *def,
                    ("Unexpected extra argument for consolidation function DEVPREDICT");
                break;
            default:
-               row_cnt = atoi(token);
-               if (row_cnt <= 0)
-                   rrd_set_error("Invalid row count: %i", row_cnt);
+                if (! convert_to_count(token, &rra_def->row_cnt, rra_def->pdp_cnt))
+                   rrd_set_error("Invalid row count: %s", token);
 #if SIZEOF_TIME_T == 4
                if ((long long) pdp_step * rra_def->pdp_cnt * row_cnt > 4294967296LL){
                    /* database timespan > 2**32, would overflow time_t */
                    rrd_set_error("The time spanned by the database is too large: must be <= 4294967296 seconds");
                }
 #endif
-               rra_def->row_cnt = row_cnt;
                break;
            }
            break;
@@ -636,6 +664,7 @@ void parseGENERIC_DS(
 {
     char      minstr[DS_NAM_SIZE], maxstr[DS_NAM_SIZE];
     char     *old_locale;
+    int       emit_failure = 1;
 
     /*
        int temp;
@@ -645,31 +674,48 @@ void parseGENERIC_DS(
        minstr,maxstr);
      */
     old_locale = setlocale(LC_NUMERIC, "C");
+    do {
+        char      numbuf[32];
+        size_t    heartbeat_len;
+        char     *colonp;
+
+        /* convert heartbeat as count or duration */
+        colonp = strchr(def, ':');
+        if (! colonp)
+            break;
+        heartbeat_len = colonp - def;
+        if (heartbeat_len >= sizeof(numbuf))
+            break;
+        strncpy (numbuf, def, heartbeat_len);
+        numbuf[heartbeat_len] = 0;
 
-    if (sscanf(def, "%lu:%18[^:]:%18[^:]",
-               &(ds_def->par[DS_mrhb_cnt].u_cnt),
-               minstr, maxstr) == 3) {
-        if (minstr[0] == 'U' && minstr[1] == 0)
-            ds_def->par[DS_min_val].u_val = DNAN;
-        else
-            ds_def->par[DS_min_val].u_val = atof(minstr);
-
-        if (maxstr[0] == 'U' && maxstr[1] == 0)
-            ds_def->par[DS_max_val].u_val = DNAN;
-        else
-            ds_def->par[DS_max_val].u_val = atof(maxstr);
-
-        if (!isnan(ds_def->par[DS_min_val].u_val) &&
-            !isnan(ds_def->par[DS_max_val].u_val) &&
-            ds_def->par[DS_min_val].u_val
-            >= ds_def->par[DS_max_val].u_val) {
-            rrd_set_error("min must be less than max in DS definition");
-            setlocale(LC_NUMERIC, old_locale);
-            return;
+        if (! convert_to_count(numbuf, &(ds_def->par[DS_mrhb_cnt].u_cnt), 1))
+            break;
+
+        if (sscanf(1+colonp, "%18[^:]:%18[^:]",
+                   minstr, maxstr) == 2) {
+            emit_failure = 0;
+            if (minstr[0] == 'U' && minstr[1] == 0)
+                ds_def->par[DS_min_val].u_val = DNAN;
+            else
+                ds_def->par[DS_min_val].u_val = atof(minstr);
+
+            if (maxstr[0] == 'U' && maxstr[1] == 0)
+                ds_def->par[DS_max_val].u_val = DNAN;
+            else
+                ds_def->par[DS_max_val].u_val = atof(maxstr);
+
+            if (!isnan(ds_def->par[DS_min_val].u_val) &&
+                !isnan(ds_def->par[DS_max_val].u_val) &&
+                ds_def->par[DS_min_val].u_val
+                >= ds_def->par[DS_max_val].u_val) {
+                rrd_set_error("min must be less than max in DS definition");
+                break;
+            }
         }
-    } else {
+    } while (0);
+    if (emit_failure)
         rrd_set_error("failed to parse data source %s", def);
-    }
     setlocale(LC_NUMERIC, old_locale);
 }