[rrd-developers] bug: "phase shift" in holt-winters predictions

Evan Miller emiller at imvu.com
Tue Aug 7 02:07:22 CEST 2007


I'd like to report a bug that appears in RRDtool 1.2.15, 
1.2.99907052400, and 1.2.99907080300.

The problem: When a Holt-Winters archive misses several updates, the 
predictions for those timestamps are postponed rather than ignored. For 
example, if data collection halts at 1 PM and resumes at 2 PM, then the 
predictions will be shifted by an hour; the 1:15 PM prediction will 
appear at 2:15 PM, the 1:30 PM prediction will appear at 2:30 PM, etc. A 
data outage can corrupt all the predictions in an archive in this way.

Below is a test script that demonstrates the problem. In the first 
season, there is a spike at T=T_0 + 4 (where T_0 is the first 
timestamp). If a season has length S, we'd expect a spike in the 
prediction for T=T_0 + S + 4; however, if I introduce a data outage for 
T=T_0 + S + 1 .. T_0 + S + 3, the prediction spike moves to T=T_0 + S + 
8. RRDtool apparently treats a season as a period of time for which 
observations exist; I believe the correct behavior is to treat a season 
as a period of time regardless of whether observations are available.

I'll try to find the source of the bug myself, but I'd appreciate any 
insight, patches, or workarounds people might have concerning it.

Evan




#!/usr/bin/perl -w

use RRDs 1.2015;
use Data::Dumper;
use strict;

# Updates an RRA, creating it if it doesn't exist
sub rrd_update($$$$$$@) {
     my ($path, $type, $time, $step, $readings, $archives, $heartbeat) = @_;

     $heartbeat ||= 5;

     if (!-f $path) {
         my @args = ($path, "--start", $time - $step, "--step", $step,
                 (map {
                  "DS:$_:$type:".($step*$heartbeat).":U:U"
                  } keys %$readings),
                 @$archives);
         RRDs::create @args;
         return "RRDtool create error: ".join(' ', @args).RRDs::error if 
RRDs::error;
     }
     my $info = RRDs::info($path);
     RRDs::update $path, "--template", join(":", sort keys %$readings),
             "$time:".join(":", map { $$readings{$_} } sort keys 
%$readings);

     return "RRDtool update error on $path: ".RRDs::error if RRDs::error;
}

sub rrd_hw_archive_definition($$$$) {
     my ($period, $alpha, $beta, $gamma) = @_;
     my $threshold = 3;
     my $window    = 5;
     return [
             "RRA:HWPREDICT:".(2*$period).":$alpha:$beta:$period:2",  # 1
             "RRA:SEASONAL:$period:$gamma:1",                         # 2
             "RRA:DEVPREDICT:".(2*$period).":4",                      # 3
             "RRA:DEVSEASONAL:$period:$gamma:1",                      # 4
             "RRA:FAILURES:$period:$threshold:$window:4",             # 5
             ];
}

my $period = 8;
my $rrd_file = "/tmp/hwshift.rrd";

unlink $rrd_file;

my $hwarchives = rrd_hw_archive_definition($period, 0.1, 0.2, 0.3);

my $step = 1;

my $time = 1000000000;

my $counter;

rrd_update $rrd_file, "COUNTER", $time, $step, { reading => $counter  = 
0 }, $hwarchives, 1;

# period 1
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
# spike occurs here
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 10}, $hwarchives;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;

# period 2
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
# missed updates
$time++; $counter += 1;
$time++; $counter += 1;
# spike occurs here again
$time++; $counter += 10;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;
rrd_update $rrd_file, "COUNTER", ++$time, $step, { reading => $counter 
+= 1 }, $hwarchives;

my ($start, $rrd_step, $names, $data) = RRDs::fetch($rrd_file, 
"HWPREDICT", "-s",
                 $time - 1, "-e", $time - 1, "-r", $step);

print "Most recent prediction should be 1, is $$data[0][0]\n";



More information about the rrd-developers mailing list