From: Peter A. Bigot Date: Thu, 15 May 2014 21:27:41 +0000 (-0500) Subject: rrdcreate: support scale suffix for heartbeat/steps/rows X-Git-Tag: v1.5.0-rc1~101^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3abaf34e094ba80c35699a86388479a3ee21eef7;p=thirdparty%2Frrdtool-1.x.git rrdcreate: support scale suffix for heartbeat/steps/rows 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 --- diff --git a/doc/rrdcreate.pod b/doc/rrdcreate.pod index d65843c1..0fe67cce 100644 --- a/doc/rrdcreate.pod +++ b/doc/rrdcreate.pod @@ -37,6 +37,8 @@ I documentation for other ways to specify time. Specifies the base interval in seconds with which data will be fed into the B. +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 defines how many of these I are used to build a I which then goes into the archive. +See also L<"STEP, HEARTBEAT, and Rows As Durations">. I defines how many generations of data values are kept in an B. 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 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 Bs: 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 + +indicates seconds + +=item C + +indicates minutes. The value is multipled by 60. + +=item C + +indicates hours. The value is multipled by 3600 (or C<60m>). + +=item C + +indicates days. The value is multipled by 86400 (or C<24h>). + +=item C + +indicates weeks. The value is multipled by 604800 (or C<7d>). + +=item C + +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 + +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. diff --git a/src/rrd_create.c b/src/rrd_create.c index b00b0bd8..4824dbb2 100644 --- a/src/rrd_create.c +++ b/src/rrd_create.c @@ -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); }