File Coverage

blib/lib/RRD/Editor.pm
Criterion Covered Total %
statement 1084 1288 84.1
branch 268 440 60.9
condition 73 167 43.7
subroutine 87 91 95.6
pod 36 36 100.0
total 1548 2022 76.5


line stmt bran cond sub pod time code
1             ############################################################
2             #
3             # RRD::Editor - Portable, pure perl tool to create and edit RRD files.
4             #
5             ############################################################
6              
7             package RRD::Editor;
8              
9 4     4   85132 use 5.8.8; # nan doesn't seem to be supported properly by perl before this
  4         14  
  4         190  
10 4     4   50 use strict;
  4         8  
  4         155  
11 4     4   17 use warnings;
  4         11  
  4         316  
12              
13             require Exporter;
14 4     4   2165 use POSIX qw/strftime/;
  4         26010  
  4         54  
15 4     4   4583 use Carp qw(croak carp cluck);
  4         6  
  4         292  
16             #use Getopt::Long qw(GetOptionsFromString :config pass_through);
17 4     4   3779 use Getopt::Long qw(:config pass_through);
  4         52920  
  4         28  
18 4     4   3040 use Time::HiRes qw(time);
  4         5614  
  4         17  
19 4     4   638 use Config;
  4         5  
  4         170  
20              
21 4     4   16 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS @ISA);
  4         5  
  4         505  
22              
23             $VERSION = '0.18';
24              
25             @ISA = qw(Exporter);
26             @EXPORT = qw();
27             @EXPORT_OK = qw(
28             open close create update info dump fetch
29             last set_last lastupdate minstep
30             DS_names DS_heartbeat set_DS_heartbeat DS_min set_DS_min DS_max set_DS_max DS_type set_DS_type rename_DS add_DS delete_DS
31             num_RRAs RRA_numrows RRA_type RRA_step RRA_xff set_RRA_xff RRA_el set_RRA_el add_RRA delete_RRA resize_RRA
32             );
33             %EXPORT_TAGS = (all => \@EXPORT_OK);
34              
35              
36             # define sizes of long ints and floats for the various file encodings
37 4     4   46 use constant NATIVE_LONG_EL_SIZE => $Config{longsize};
  4         4  
  4         3039  
38 4     4   9967 use constant NATIVE_DOUBLE_EL_SIZE => $Config{doublesize};
  4         8  
  4         120  
39 4     4   254 use constant PORTABLE_LONG_EL_SIZE => 4; # 32 bits
  4         4  
  4         166  
40 4     4   19 use constant PORTABLE_SINGLE_EL_SIZE => 4; # IEEE 754 single is 32 bits
  4         5  
  4         158  
41 4     4   25 use constant PORTABLE_DOUBLE_EL_SIZE => 8; # IEEE 754 double is 64 bits
  4         5  
  4         434  
42              
43              
44             # try to figure out the endian-ness of this machine; assumes IEEE 754 doubles which should be ok on all modern machines
45             sub _endian {
46 4     4   13 my $endiantest=unpack("h*", pack("d","1000.1234"));
47 4 50       12 if ($endiantest eq "c92a329bcf04f804") {
    0          
    0          
48 4         978 return "little";
49             } elsif ($endiantest eq "04f804cf9b322ac9") {
50 0         0 return "big"; # big endian
51             } elsif ($endiantest eq "cf04f804c92a329b") {
52 0         0 return "mixed"; # mixed endian (used by some ARM processors)
53             } else {
54 0         0 return "unknown";
55             }
56             }
57 4     4   20 use constant ENDIAN => _endian();
  4         5  
  4         8  
58            
59             # sort out the float cookie used by RRD files
60             sub _cookie {
61             # Getting RRD file float cookie right is a little tricky because Perl rounds 8.642135E130 up to 8.6421500000000028e+130 on
62             # Intel 32 bit machines, and rounds to something else on 64 bit machines, and neither of these give the same bit sequence as
63             # C when Perl stores 8.642135E130. Sigh ...
64            
65             # See if we can make a call to C to get the float cookie. Reliable, but needs Inline module to be available.
66 4     4   2 eval {
67 4         69 load Inline C => <<'END';
68             double _cookie_C() {
69             return 8.642135E130;
70             }
71             END
72 0         0 return pack("d",_cookie_C());
73             };
74             # Inline not available.
75             # Try approach that avoids need for Inline module. Ok so long as machine uses IEEE doubles (i.e. seems like all modern machines)
76             # and little-endian, big-endian and mixed-endian byte order (all modern machines that I know of, museum pieces excepted):
77 4         6 if (ENDIAN eq "little") {
78 4         262 return chr(47). chr(37). chr(192). chr(199). chr(67). chr(43). chr(31). chr(91); # little endian
79             } elsif (ENDIAN eq "big") {
80             return chr(91). chr(31). chr(43). chr(67). chr(199). chr(192). chr(37). chr(47); # big endian
81             } elsif (ENDIAN eq "mixed") {
82             return chr(67). chr(43). chr(31). chr(91). chr(47). chr(37). chr(192). chr(199); # mixed endian (used by some ARM processors)
83             } else {
84             warn("Warning: To work with native (non-portable) RRD files, you need to install the perl Inline C module (e.g. by typing 'cpan -i Inline')\n");
85             return chr(67). chr(43). chr(31). chr(91). chr(47). chr(37). chr(192). chr(199);
86             }
87             }
88 4     4   20 use constant DOUBLE_FLOATCOOKIE => 8.642135E130;
  4         5  
  4         182  
89 4     4   18 use constant NATIVE_BINARY_FLOATCOOKIE => _cookie();
  4         5  
  4         8  
90 4     4   17 use constant PORTABLE_BINARY_FLOATCOOKIE => chr(47). chr(37). chr(192). chr(199). chr(67). chr(43). chr(31). chr(91); # portable format is always little-endian
  4         3  
  4         178  
91 4     4   16 use constant SINGLE_FLOATCOOKIE => 8.6421343830016e+13; # cookie to use when storing floats in single precision as +130 exponent on old cookie is too large
  4         7  
  4         3187  
92              
93             sub _native_double {
94             # try to figure out the long/double alignment needed for the RRDTOOL file format
95 0 0 0 0   0 if ($Config{myarchname} =~ m/(sun|sparc|mips|irix|ppc|powerpc|arm)/i && NATIVE_LONG_EL_SIZE==4) {
96             # Only affects behaviour when writing new files from scratch, otherwise can figure out the right alignment to use when
97             # reading an existing file
98             # Align longs on 32 bit boundaries and doubles on 64 bit boundaries.
99 0         0 return "native-double-mixed";
100             } else {
101             # Intel/AMD processors, DEC alpha processors - should probably swap these checks around since most machines are double-mixed ?
102             # Otherwise, align longs/doubles on 32 bit machines on 32 bit boundaries, and 64 bit machines on 64 bit boundaries.
103 0         0 return "native-double-simple";
104             }
105             }
106              
107             # check whether perl pack function supports little-endian usage:
108             eval {
109             my $test=pack("d<",\(DOUBLE_FLOATCOOKIE));
110             };
111             our $PACK_LITTLE_ENDIAN_SUPPORT = (length($@)>0 ? 0 : 1);
112              
113             # Define NaN, Inf, -Inf. Not as easy as it sounds - usually "nan", "inf", "-inf" works, but not always e.g. on older versions of perl, on SH4 etc
114             sub _isNan {
115 2025   66 2025   10846 return $_[0] eq "nan" || $_[0] != $_[0]; # NaN is the only quantity that does not equal itself
116             }
117             sub _isInf {
118 1135   100 1135   3825 return $_[0] > 0 && ($_[0]*10 == $_[0]); # Inf remains equal to itself after arithmetic
119             }
120             sub _NaN {
121             # try using the IEEE 754 NaN bit pattern
122 4     4   6 my $nan;
123 4         5 if (ENDIAN eq "little") {
124 4         20 $nan= unpack("d", scalar reverse pack "H*", "7FF8000000000000");# little endian
125 4 50       9 if (_isNan($nan)) { return $nan;}
  4         221  
126             } elsif (ENDIAN eq "big") {
127             $nan= unpack("d", scalar pack "H*", "7FF8000000000000"); # big endian
128             if (_isNan($nan)) { return $nan;}
129             } elsif (ENDIAN eq "mixed") {
130             $nan= unpack("d", scalar pack "H*", "000000007FF80000"); # mixed endian (used by some ARM processors)
131             if (_isNan($nan)) { return $nan;}
132             }
133             # last ditch attempt. try perl "nan" string. Doesn't work on all OS's since its relies on the interpretation made by a native C library call
134 0         0 $nan = 0+"nan";
135 0 0       0 if (_isNan($nan)) {return $nan;}
  0         0  
136 0         0 warn("Warning: Looks like you have no NaN support. Might have problems reading rrd files.");
137 0         0 return 0;
138             }
139             sub _Inf {
140             # try using the IEEE 754 Inf bit pattern
141 4     4   11 my $inf;
142 4         4 if (ENDIAN eq "little") {
143 4         12 $inf= unpack("d", scalar reverse pack "H*", "7FF0000000000000");# little endian
144 4 50       10 if (_isInf($inf)) {return $inf;}
  4         215  
145             } elsif (ENDIAN eq "big") {
146             $inf= unpack("d", scalar pack "H*", "7FF0000000000000"); # big endian
147             if (_isInf($inf)) {return $inf;}
148             } elsif (ENDIAN eq "mixed") {
149             $inf= unpack("d", scalar pack "H*", "000000007FF00000"); # mixed endian (used by some ARM processors)
150             if (_isInf($inf)) {return $inf;}
151             }
152             # didn't work.
153 0         0 $inf = 0+"inf";
154 0 0       0 if (_isInf($inf)) {return $inf;}
  0         0  
155 0         0 warn("Warning: Looks like you have no Inf support. Might have problems reading rrd files.");
156 0         0 return 1;
157             }
158             sub _strfloat {
159             # convert a float to a string in a standard way i.e. removing cross-platform variation in strings displayed for nan and inf
160 805 100   805   1105 if (_isNan($_[0])) {
    100          
    100          
161 232         859 return "nan";
162             } elsif (_isInf($_[0])) {
163 19         40 return "inf" ;
164             } elsif (_isInf(-$_[0])) {
165 19         44 return "-inf" ;
166             } else {
167 535         482 my $digits=10;
168 535 50       804 if ($_[1]) {$digits=$_[1];}
  535         501  
169 535         2158 my $str=sprintf "%0.".$digits."e",$_[0];
170 535 50       2281 if ($str =~ m/^([+|-]?\d*[.]?\d*e[+|-]?)0(\d\d)$/) {$str=$1.$2; } # for windows - convert 3 digit exponent to 2 digits
  0         0  
171 535         1886 return $str;
172             }
173             }
174             sub _strint {
175             # convert an integer to a string in a standard way i.e. removing cross-platform variation in strings displayed for nan and inf
176 40 100   40   67 if (_isNan($_[0])) {
    50          
    50          
177 38         135 return "nan";
178             } elsif (_isInf($_[0])) {
179 0         0 return "inf" ;
180             } elsif (_isInf(-$_[0])) {
181 0         0 return "-inf" ;
182             } else {
183 2         9 return sprintf "%d",$_[0];
184             }
185             }
186              
187 4     4   27 use constant NAN => _NaN();
  4         5  
  4         6  
188 4     4   20 use constant INF => _Inf();
  4         3  
  4         26  
189              
190             # define index into elements in CDP_PREP array
191 4     4   20 use constant VAL => 0;
  4         4  
  4         188  
192 4     4   19 use constant UNKN_PDP_CNT => 1;
  4         8  
  4         157  
193 4     4   14 use constant HW_INTERCEPT => 2;
  4         5  
  4         250  
194 4     4   51 use constant HW_LAST_INTERCEPT => 3;
  4         5  
  4         150  
195 4     4   16 use constant HW_SLOPE => 4;
  4         3  
  4         163  
196 4     4   17 use constant HW_LAST_SLOPE => 5;
  4         5  
  4         187  
197 4     4   18 use constant NULL_COUNT => 6;
  4         6  
  4         177  
198 4     4   30 use constant LAST_NULL_COUNT=> 7;
  4         6  
  4         169  
199 4     4   19 use constant PRIMARY_VAL => 8;
  4         4  
  4         146  
200 4     4   16 use constant SECONDARY_VAL => 9;
  4         2  
  4         58659  
201              
202             ###### private functions
203              
204             # older versions of Getopt::Long (e.g. used on Mac OS X) don't have this function, so lets add it explicitly
205             sub _GetOptionsFromString(@) {
206 17     17   31 my ($string) = shift;
207 17         2116 require Text::ParseWords;
208 17         4235 my @temp=@ARGV;
209 17         55 @ARGV = Text::ParseWords::shellwords($string);
210 17         1744 my $ret = GetOptions(@_);
211 17         5056 my @args=@ARGV;
212 17         33 @ARGV=@temp;
213 17         54 return ( $ret, \@args );
214             }
215              
216             ### used to extract information from raw RRD file and build corresponding structured arrays
217             sub _get_header_size {
218             # size of file header, in bytes
219 31     31   41 my $self = $_[0]; my $rrd=$self->{rrd};
  31         54  
220            
221 31         272 return $self->{DS_DEF_IDX} +
222             $self->{DS_EL_SIZE} * $rrd->{ds_cnt} +
223             $self->{RRA_DEF_EL_SIZE} * $rrd->{rra_cnt} +
224             $self->{LIVE_HEAD_SIZE} +
225             $self->{PDP_PREP_EL_SIZE} * $rrd->{ds_cnt} +
226             $self->{CDP_PREP_EL_SIZE} * $rrd->{ds_cnt} * $rrd->{rra_cnt} +
227             $self->{RRA_PTR_EL_SIZE} * $rrd->{rra_cnt}
228             +$self->{HEADER_PAD};
229             }
230              
231             ####
232             sub _packd {
233             # pack an array of doubles into a binary string, format determined by $self->{encoding}
234             # - will do packing manually if necessary, to guarantee portability
235             #my ($self,$list_ptr,$encoding) = @_;
236 352     352   470 my $encoding=$_[0]->{encoding};
237 352 100       555 if (defined($_[2])) {$encoding=$_[2];}
  2         2  
238              
239 352 50 33     2956 if ($encoding eq "native-double-simple" || $encoding eq "native-double-mixed") {
    50 33        
    50 66        
    100          
240             # backwards-compatible (with RRDTOOL) RRD format
241 0         0 return pack("d*", @{$_[1]});
  0         0  
242             } elsif ($encoding eq "native-single") {
243             # save some work - we can pack a portable-single using native float
244 0         0 return pack("f*", @{$_[1]});
  0         0  
245             } elsif ($PACK_LITTLE_ENDIAN_SUPPORT && $encoding eq "litteendian-single") {
246             # save some work - we can pack a portable-single using native float
247 0         0 return pack("f<*", @{$_[1]});
  0         0  
248             } elsif ($PACK_LITTLE_ENDIAN_SUPPORT && $encoding eq "littleendian-double") {
249             # shortcut - only difference from portable format is that native format is big-endian
250 150         159 return pack("d<*", @{$_[1]});
  150         798  
251             }
252 202         139 my $f; my $sign; my $shift; my $exp; my $mant; my $string=''; my $significand; my $significandlo; my $significandhi;
  0         0  
  0         0  
  0         0  
  202         165  
  202         125  
  0         0  
253 202 100 66     604 if ($encoding eq "portable-single" || $encoding eq "ieee-32") {
    50 33        
254             # manually pack an IEEE 754 32bit single precision number in little-endian order
255 75         58 for (my $i=0; $i<@{$_[1]}; $i++) {
  234         324  
256 159         93 $f=@{$_[1]}[$i];
  159         157  
257 159 100       164 if (_isNan($f)) {
    100          
    100          
    100          
258 16         10 $sign=0; $exp=255; $significand=1;
  16         12  
  16         12  
259             } elsif ($f == -1 * INF) {
260 4         4 $sign=1; $exp=255; $significand=0;
  4         4  
  4         3  
261             } elsif ($f == INF) {
262 4         4 $sign=0; $exp=255; $significand=0;
  4         2  
  4         4  
263             } elsif ($f == 0) {
264 92         99 $sign=0; $exp=0; $significand=0;
  92         63  
  92         56  
265             } else {
266 43 100       51 $sign = ($f<0) ? 1 : 0;
267 43 100       40 $f = ($f<0) ? -$f : $f;
268             # get the normalized form of f and track the exponent
269 43         30 $shift = 0;
270 43         64 while($f >= 2) { $f /= 2; $shift++; }
  284         218  
  284         336  
271 43   66     88 while($f < 1 && $f>0) { $f *= 2; $shift--; }
  71         43  
  71         168  
272 43         33 $f -= 1;
273             # calculate the binary form (non-float) of the significand data
274 43         34 $significand = int($f*(2**23));
275             # get the biased exponent
276 43         30 $exp = int($shift + ((1<<7) - 1)); # shift + bias
277             }
278 159         279 $string.=pack("V",($sign<<31) | ($exp<<23) | $significand);
279             }
280 75         112 return $string;
281             } elsif ($encoding eq "portable-double" || $encoding eq "ieee-64") {
282             # manuallly pack IEEE 754 64 bit double precision in little-endian order
283 127         104 for (my $i=0; $i<@{$_[1]}; $i++) {
  500         869  
284 373         255 $f=@{$_[1]}[$i];
  373         411  
285 373 100       470 if (_isNan($f)) {
    100          
    100          
    100          
286 136         105 $sign=0; $exp=2047; $significandhi=1;$significandlo=1;
  136         103  
  136         85  
  136         101  
287             } elsif ($f == -1 * INF) {
288 4         4 $sign=1; $exp=2047; $significandhi=0;$significandlo=0;
  4         3  
  4         3  
  4         4  
289             } elsif ($f == INF) {
290 4         4 $sign=0; $exp=2047; $significandhi=0;$significandlo=0;
  4         3  
  4         2  
  4         4  
291             } elsif ($f ==0) {
292 93         69 $sign=0; $exp=0; $significandhi=0;$significandlo=0;
  93         69  
  93         52  
  93         71  
293             } else {
294 136 100       166 $sign = ($f<0) ? 1 : 0;
295 136 100       191 $f = ($f<0) ? -$f : $f;
296             # get the normalized form of f and track the exponent
297 136         107 $shift = 0;
298 136         204 while($f >= 2) { $f /= 2; $shift++; }
  607         420  
  607         756  
299 136   66     321 while($f < 1 && $f>0 ) { $f *= 2; $shift--; }
  210         143  
  210         486  
300 136         101 $f -= 1;
301             # calculate the binary form (non-float) of the significand data
302 136         148 $significandhi = int($f*(2**20));
303 136         135 $significandlo = int( ($f-$significandhi/(2**20))*(2**52));
304             # get the biased exponent
305 136         120 $exp = int($shift + ((1<<10) - 1)); # shift + bias
306             }
307 373         833 $string.=pack("V V",$significandlo, ($sign<<31) | ($exp<<20) | $significandhi);
308             }
309 127         390 return $string;
310             } else {
311 0         0 croak("packd:unknown encoding: ".$encoding."\n");
312             }
313             }
314              
315             #####
316              
317             sub _unpackd {
318             # unpack binary string into array of doubles, format determined by $self->{encoding}
319             # - will do unpacking manually if necessary, to guarantee portability
320             #my ($self, $string, $encoding) = @_;
321 587     587   874 my $encoding=$_[0]->{encoding};
322 587 100       1004 if (defined($_[2])) {$encoding=$_[2];}
  30         40  
323            
324 587 50 33     5059 if ($encoding eq "native-double-simple" || $encoding eq "native-double-mixed") {
    100 66        
    100 66        
    100          
325             # backwards-compatible (with RRDTOOL) RRD format
326 0         0 return unpack("d*", $_[1]);
327             } elsif ($encoding eq "native-single" ) {
328             # save some work - we can unpack portable-single using native float
329 19         59 return unpack("f*", $_[1]);
330             } elsif ($PACK_LITTLE_ENDIAN_SUPPORT && $encoding eq "littleendian-single" ) {
331             # save some work - we can unpack portable-single using native float
332 6         28 return unpack("f<*", $_[1]);
333             } elsif ($PACK_LITTLE_ENDIAN_SUPPORT && $encoding eq "littleendian-double") {
334             # shortcut - only difference from portable format is that native format is big-endian
335 525         1802 return unpack("d<*", $_[1]);
336             }
337 37         39 my $word; my $sign; my $expo; my $mant; my $manthi; my $mantlo; my @list; my $num; my $i;
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
338 37 100 66     164 if ($encoding eq "portable-single" || $encoding eq "ieee-32") {
    50 33        
339             # manually unpack a little-endian IEEE 754 32bit single-precision number
340 6         35 for ($i=0; $i
341 6         49 $word = (unpack("C",substr($_[1],$i+3,1)) << 24) + (unpack("C",substr($_[1],$i+2,1)) << 16) + (unpack("C",substr($_[1],$i+1,1)) << 8) + unpack("C",substr($_[1],$i,1));
342 6         17 $expo = (($word & 0x7F800000) >> 23) - 127;
343 6         15 $mant = (($word & 0x007FFFFF) | 0x00800000);
344 6 100       21 $sign = ($word & 0x80000000) ? -1 : 1;
345 6 50 33     50 if ($expo == 128 && $mant == 0 ) {
    50 33        
    50          
346 0 0       0 $num=$sign>0 ? INF : -1 * INF;
347             } elsif ($expo == 128) {
348 0         0 $num=NAN;
349             } elsif ($expo == -127 && $mant ==0) {
350 0         0 $num=0;
351             } else {
352 6         58 $num = $sign * (2**($expo-23))*$mant;
353             }
354 6         25 push (@list, $num);
355             }
356 6         20 return @list;
357             } elsif ($encoding eq "portable-double" || $encoding eq "ieee-64") {
358             # manually unpack IEEE 754 64 bit double-precision number.
359 31         84 for ($i=0; $i
360 106         271 $word = (unpack("C",substr($_[1],$i+7,1)) << 24) + (unpack("C",substr($_[1],$i+6,1)) << 16) + (unpack("C",substr($_[1],$i+5,1)) << 8) + unpack("C",substr($_[1],$i+4,1));
361 106         231 $mantlo = (unpack("C",substr($_[1],$i+3,1)) << 24) + (unpack("C",substr($_[1],$i+2,1)) << 16) + (unpack("C",substr($_[1],$i+1,1)) << 8) + unpack("C",substr($_[1],$i,1));
362 106         100 $expo = (($word & 0x7FF00000) >> 20) - 1023;
363 106         70 $manthi = ($word & 0x000FFFFF) ;
364 106 100       130 $sign = ($word & 0x80000000) ? -1 : 1;
365 106 50 66     393 if ($expo == 1024 && $mantlo == 0 && $manthi==0 ) {
    100 33        
    100 100        
      66        
366 0         0 $num=$sign * INF;
367             } elsif ($expo == 1024) {
368 16         15 $num=NAN;
369             } elsif ($expo==-1023 && $manthi==0 && $mantlo==0) {
370 1         1 $num=0;
371             } else {
372 89         205 $num = $sign * ( (2**$expo) + (2**($expo-20))*$manthi + (2**($expo-52))*$mantlo );
373             }
374 106         174 push (@list, $num);
375             }
376 31         85 return @list;
377             } else {
378 0         0 croak("unpackd:unknown encoding: ".$encoding."\n");
379             }
380             }
381              
382             #####
383             sub _packlongchar {
384             # pack encoding specification for integers. no need for manual packing/unpacking of integers as agreed portable formats already available
385 39     39   48 my $self=$_[0];
386 39 50 33     181 if ($self->{encoding} eq "native-double-simple" || $self->{encoding} eq "native-double-mixed") {
387             # backwards-compatible (with RRDTOOL) RRD format
388 0         0 return "L!"; # native long int
389             } else {
390             # portable format, little-endian 32bit long int
391 39         85 return "V";
392             }
393             }
394              
395             ####
396             sub _sizes {
397             # define the sizes of the various elements in RRD binary file
398 10     10   18 my ($self)=@_;
399            
400 10         23 $self->{OFFSET} = 12; # byte position of start of float cookie.
401 10         14 $self->{RRA_DEL_PAD} = 0; # for byte alignment in RRA_DEF after char(20) string
402 10         18 $self->{STAT_PAD} = 0; # for byte alignment at end of static header.
403 10         22 $self->{RRA_PAD} = 0; # for byte alignment at end of RRAD_DEF float array
404 10 50 33     215 if ($self->{encoding} eq "native-double-simple" || $self->{encoding} eq "native-double-mixed") {
    100 66        
    50 100        
      66        
      66        
      33        
405 0         0 $self->{LONG_EL_SIZE} = NATIVE_LONG_EL_SIZE;
406 0         0 $self->{FLOAT_EL_SIZE}= NATIVE_DOUBLE_EL_SIZE;
407 0         0 $self->{COOKIE} = NATIVE_BINARY_FLOATCOOKIE;
408 0         0 if ( NATIVE_LONG_EL_SIZE == 8) {
409             # long ints and doubles are both 64 bits, alignment is at 64 bit boundaries for both native-double-simple and native-double-mixed
410 0         0 $self->{OFFSET} = 16; # 64 bit alignment of the float cookie
411 0         0 $self->{RRA_DEL_PAD} = 4; # 64 bit alignment for first long in RRA_DEF after char(20) string
412             } elsif ( NATIVE_LONG_EL_SIZE == 4 && $self->{encoding} eq "native-double-mixed") {
413             # 32 bit native-double-mixed: align long ints at 32 bit boundaries and doubles at 64 bit boundaries.
414             $self->{OFFSET} = 16; # 64 bit alignment of the float cookie
415             $self->{RRA_DEL_PAD} = 0; # 32 bit alignment first long in RRA_DEF after char(20) string
416             $self->{STAT_PAD} = 4; # 64 bit alignment for start of DS defs
417             $self->{RRA_PAD} = 4; # 64 bit alignment for first double in RRA_DEF
418             } else {
419             # default is 32 bit native-double-simple: align both long ints and doubles at 32 bit boundaries.
420             }
421             } elsif ($self->{encoding} eq "littleendian-single" || $self->{encoding} eq "native-single" || $self->{encoding} eq "portable-single" || $self->{encoding} eq "ieee-32") {
422 2         4 $self->{LONG_EL_SIZE} = PORTABLE_LONG_EL_SIZE;
423 2         1 $self->{FLOAT_EL_SIZE}= PORTABLE_SINGLE_EL_SIZE; # 32 bits
424 2         4 my @cookie=(SINGLE_FLOATCOOKIE);
425 2         5 $self->{COOKIE} = $self->_packd(\@cookie,"portable-single");
426             } elsif ($self->{encoding} eq "littleendian-double" || $self->{encoding} eq "portable-double" || $self->{encoding} eq "ieee-64") {
427 8         19 $self->{LONG_EL_SIZE} = PORTABLE_LONG_EL_SIZE;
428 8         16 $self->{FLOAT_EL_SIZE}= PORTABLE_DOUBLE_EL_SIZE; # 64 bits
429 8         23 $self->{COOKIE} = PORTABLE_BINARY_FLOATCOOKIE;
430             }
431 10         33 $self->{DIFF_SIZE} = $self->{FLOAT_EL_SIZE} - $self->{LONG_EL_SIZE};
432 10         37 $self->{STAT_HEADER_SIZE} = $self->{OFFSET} + $self->{FLOAT_EL_SIZE} + 3 * $self->{LONG_EL_SIZE};
433 10         30 $self->{STAT_HEADER_SIZE0} = $self->{STAT_HEADER_SIZE} + 10 * $self->{FLOAT_EL_SIZE} + $self->{STAT_PAD};
434 10         20 $self->{RRA_PTR_EL_SIZE} = $self->{LONG_EL_SIZE};
435 10         27 $self->{CDP_PREP_EL_SIZE} = 10 * $self->{FLOAT_EL_SIZE};
436 10         18 $self->{PDP_PREP_PAD} = 2; # for byte alignment of char(30) string in PDP_PREP
437 10         34 $self->{PDP_PREP_EL_SIZE} = 30 + $self->{PDP_PREP_PAD} + 10 * $self->{FLOAT_EL_SIZE};
438 10         37 $self->{RRA_DEF_EL_SIZE} = 20 + $self->{RRA_DEL_PAD} + 2 * $self->{LONG_EL_SIZE} + 10 * $self->{FLOAT_EL_SIZE} +$self->{RRA_PAD};
439 10         18 $self->{DS_DEF_IDX} = $self->{STAT_HEADER_SIZE0};
440 10         21 $self->{DS_EL_SIZE} = 40 + 10 * $self->{FLOAT_EL_SIZE} ;
441 10         23 $self->{LIVE_HEAD_SIZE} = 2 * $self->{LONG_EL_SIZE};
442 10         20 $self->{HEADER_PAD} = 0; # accounting for pad bytes at end of header (e.g. 8 pad bytes are added on Linux/Intel 64 bit platforms)
443             }
444              
445             ####
446             sub _extractDSdefs {
447             # extract DS definitions from raw header (which must have been already read using rrd_open)
448 6     6   11 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         11  
449            
450 6         7 my $i;
451 6         17 my $L=$self->_packlongchar();
452 6         15 @{$rrd->{ds}}=[];
  6         63  
453 6         28 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
454 24         30 my $ds={};
455             #($ds->{name}, $ds->{type}, $ds->{hb}, $ds->{min}, $ds->{max})= unpack("Z20 Z20 $L x".DIFF_SIZE." d d",substr(${$header},$idx,DS_EL_SIZE));
456 24         35 ($ds->{name}, $ds->{type}, $ds->{hb})= unpack("Z20 Z20 $L",substr(${$header},$idx,40+$self->{LONG_EL_SIZE}));
  24         135  
457 24         36 ($ds->{min}, $ds->{max})= $self->_unpackd(substr(${$header},$idx+40+$self->{FLOAT_EL_SIZE},2*$self->{FLOAT_EL_SIZE}));
  24         78  
458 24         47 $rrd->{ds}[$i] = $ds;
459 24         71 $idx+=$self->{DS_EL_SIZE};
460             #print $ds->{name}," ",$ds->{type}," ",$ds->{hb}," ",$ds->{min}," ",$ds->{max},"\n";
461             }
462             }
463              
464             ###
465             sub _extractRRAdefs {
466             # extract RRA definitions from raw header (which must have been already read using rrd_open)
467 6     6   11 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         20  
468              
469 6         8 my $i;
470 6         14 my $L=$self->_packlongchar();
471 6         15 @{$rrd->{rra}}=[];
  6         140  
472 6         28 for ($i=0; $i<$rrd->{rra_cnt}; $i++) {
473 30         37 my $rra={};
474 30         74 ($rra->{name}, $rra->{row_cnt}, $rra->{pdp_cnt})= unpack("Z".(20+$self->{RRA_DEL_PAD})." $L $L",substr(${$header},$idx,20+$self->{RRA_DEL_PAD}+2*$self->{LONG_EL_SIZE}));
  30         147  
475 30         41 ($rra->{xff})= $self->_unpackd(substr(${$header},$idx+20+$self->{RRA_DEL_PAD} + 2*$self->{LONG_EL_SIZE}+$self->{RRA_PAD}, $self->{FLOAT_EL_SIZE}));
  30         95  
476 30         58 $rrd->{rra}[$i] = $rra;
477 30         79 $idx+=$self->{RRA_DEF_EL_SIZE};
478             #print $rra->{name}," ",$rra->{row_cnt}," ",$rra->{pdp_cnt}," ",$rra->{xff},"\n";
479             }
480             }
481              
482             ####
483             sub _extractPDPprep {
484             # extract PDP prep from raw header (which must have been already read using rrd_open)
485 6     6   11 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         13  
486              
487 6         9 my $i;
488 6         17 my $L=$self->_packlongchar();
489 6         13 @{$rrd->{pdp_prep}}=[];
  6         19  
490 6         28 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
491 24         29 my $pdp={};
492 24         52 ($pdp->{last_ds}, $pdp->{unkn_sec_cnt})= unpack("Z".(30+$self->{PDP_PREP_PAD})." $L",substr(${$header},$idx,30+$self->{PDP_PREP_PAD}+$self->{LONG_EL_SIZE})); # NB Z32 instead of Z30 due to byte alignment
  24         104  
493 24         30 ($pdp->{val})= $self->_unpackd(substr(${$header},$idx+30+$self->{PDP_PREP_PAD}+$self->{FLOAT_EL_SIZE},$self->{FLOAT_EL_SIZE}));
  24         86  
494 24         59 $rrd->{ds}[$i]->{pdp_prep} = $pdp;
495 24         69 $idx+=$self->{PDP_PREP_EL_SIZE};
496             #print $pdp->{last_ds}," ",$pdp->{unkn_sec_cnt}," ",$pdp->{val},"\n";
497             }
498             }
499              
500             ###
501             sub _extractCDPprep {
502             # extract CDP prep from raw header (which must have been already read using rrd_open)
503 6     6   10 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         13  
504            
505 6         7 my $i; my $ii;
506 6         16 my $L=$self->_packlongchar();
507 6         24 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
508             #@{$rrd->{cdp_prep}[$ii]}=[];
509 30         77 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
510             # do a bit of code optimisation to aggregate function calls and array allocation here, since run inside inner loop.
511 120 50 33     499 if ($self->{encoding} eq "native-double-simple" || $self->{encoding} eq "native-double-mixed") {
    100          
512 0         0 @{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]} = unpack("d $L x".$self->{DIFF_SIZE}." d d d d $L x".$self->{DIFF_SIZE}." $L x".$self->{DIFF_SIZE}." d d",substr(${$header},$idx,$self->{CDP_PREP_EL_SIZE}));
  0         0  
  0         0  
513 0         0 $idx+=$self->{CDP_PREP_EL_SIZE};
514             } elsif ($self->{encoding} eq "native-single") {
515 20         36 @{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]} = unpack("f $L x".$self->{DIFF_SIZE}." f f f f $L x".$self->{DIFF_SIZE}." $L x".$self->{DIFF_SIZE}." f f",substr(${$header},$idx,$self->{CDP_PREP_EL_SIZE}));
  20         46  
  20         41  
516 20         42 $idx+=$self->{CDP_PREP_EL_SIZE};
517             } else {
518 100         106 @{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}=(0,0,0,0,0,0,0,0,0,0); # pre-allocate array
  100         384  
519 100         110 (@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[0])=$self->_unpackd(substr(${$header},$idx,$self->{FLOAT_EL_SIZE})); $idx+=$self->{FLOAT_EL_SIZE};
  100         250  
  100         262  
  100         217  
520 100         152 @{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[1]=unpack("$L x".$self->{DIFF_SIZE},substr(${$header},$idx,$self->{FLOAT_EL_SIZE})); $idx+=$self->{FLOAT_EL_SIZE};
  100         228  
  100         227  
  100         128  
521 100         99 @{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[2..5]=$self->_unpackd(substr(${$header},$idx,4*$self->{FLOAT_EL_SIZE})); $idx+=4*$self->{FLOAT_EL_SIZE};
  100         302  
  100         252  
  100         203  
522 100         217 @{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[6..7]=unpack("$L x".$self->{DIFF_SIZE}." $L x".$self->{DIFF_SIZE},substr(${$header},$idx,2*$self->{FLOAT_EL_SIZE})); $idx+=2*$self->{FLOAT_EL_SIZE};
  100         204  
  100         246  
  100         157  
523 100         103 @{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[8..9]=$self->_unpackd(substr(${$header},$idx,2*$self->{FLOAT_EL_SIZE})); $idx+=2*$self->{FLOAT_EL_SIZE};
  100         289  
  100         241  
  100         375  
524             }
525             }
526             }
527             }
528              
529             ###
530             sub _extractRRAptr {
531             # array of { cur_row } pointers into current row in rra from raw header (which must have been already read using rrd_open)
532 6     6   11 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         13  
533              
534 6         17 my $L=$self->_packlongchar();
535 6         17 my @ptr=unpack("$L*",substr(${$header},$idx,$self->{RRA_PTR_EL_SIZE}*$rrd->{rra_cnt}));
  6         33  
536 6         7 my $i;
537 6         23 for ($i=0; $i<$rrd->{rra_cnt}; $i++) {
538 30         85 $rrd->{rra}[$i]->{ptr}=$ptr[$i];
539             }
540             #print @ptr;
541             }
542              
543             ###
544             sub _loadRRAdata {
545             # read in and extract the RRA data. assumes rrd_open has been called to read in file header and
546             # populate the RRD data structure
547 4     4   8 my $self = $_[0]; my $rrd=$self->{rrd};
  4         10  
548 4 50       16 if (!defined($self->{fd})) {croak("loadRRDdata: must call open() first\n");}
  0         0  
549              
550 4         7 my $data; my $ds_cnt=$self->{FLOAT_EL_SIZE} * $rrd->{ds_cnt};
  4         14  
551 4         19 seek $self->{fd},$self->_get_header_size,0; # move to start of RRA data within file
552 4         26 for (my $ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
553 20         32 my $idx=0;
554 20         101 read($self->{fd}, $data, $self->{FLOAT_EL_SIZE} * $rrd->{ds_cnt}* $rrd->{rra}[$ii]->{row_cnt} );
555 20         33 my $row_cnt=$rrd->{rra}[$ii]->{row_cnt};
556 20         48 for (my $i=0; $i<$row_cnt; $i++) {
557             # rather than unpack here, do "lazy" unpacks i.e. only when needed - much faster
558             #@{$rrd->{rra}[$ii]->{data}[$i]}=unpack("d*",substr($data,$idx,$ds_cnt}) );
559 100         218 $rrd->{rra}[$ii]->{data}[$i]=substr($data,$idx,$ds_cnt);
560 100         216 $idx+=$ds_cnt;
561             }
562             #print "rra $ii:", join(", ",@{$rrd->{rra_data}[$ii][$rrd->{rra_ptr}[$ii]+1]}),"\n";
563             }
564 4         13 $rrd->{dataloaded}=1; # record the fact that the data is now loaded in memory
565             }
566              
567             ####
568             sub _findDSidx {
569             # find the index of a DS given its name
570 23     23   35 my ($self, $name) = @_; my $rrd=$self->{rrd};
  23         33  
571 23         22 my $i;
572 23         69 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
573 39 100       115 if ($rrd->{ds}[$i]->{name} eq $name) {
574 23         55 return $i;
575             }
576             }
577 0         0 return -1; # unknown source
578             }
579              
580             ################ public functions
581             sub new {
582             # create new object
583 3     3 1 1480 my $self;
584 3         12 $self->{file_name}=undef; # name of RRD file
585 3         9 $self->{fd}=undef; # file handle
586 3         9 $self->{encoding}=undef; # binary encoding within file.
587 3         11 $self->{rrd}->{version}=undef;
588 3         8 $self->{rrd}->{rra_cnt}= undef; # number of RRAs
589 3         10 $self->{rrd}->{ds_cnt}=undef; # number of DSs
590 3         8 $self->{rrd}->{pdp_step}=undef; # min time step size
591 3         7 $self->{rrd}->{last_up} = undef; # time when last updated
592 3         9 $self->{rrd}->{ds}=undef; # array of DS definitions
593 3         9 $self->{rrd}->{rra}=undef; # array of RRA info
594 3         10 $self->{rrd}->{dataloaded}=undef; # has body of RRD file been loaded into memory ?
595 3         6 bless $self;
596 3         13 return $self;
597             }
598              
599             sub DS_names {
600             # return a list containing the names of the DS's in the RRD database.
601 2     2 1 4 my $rrd=$_[0]->{rrd};
602 2         3 my @names=(); my $i;
  2         3  
603 2         8 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
604 8         18 push(@names, $rrd->{ds}[$i]->{name});
605             }
606 2         10 return @names;
607             }
608              
609             sub num_RRAs {
610             # returns the number of RRA's in the database. RRAs are indexed from 0 .. num_RRAs-1.
611 0     0 1 0 return $_[0]->{rrd}->{rra_cnt};
612             }
613              
614             sub RRA_numrows {
615             # return number of rows in a RRA
616 2     2 1 3 my ($self, $rraidx) = @_; my $rrd=$self->{rrd};
  2         4  
617 2 50 33     9 if ($rraidx > $rrd->{rra_cnt} || $rraidx<0) {croak("RRA index out of range\n");}
  0         0  
618 2         9 return $rrd->{rra}[$rraidx]->{row_cnt};
619             }
620              
621             sub RRA_type {
622             # return the type of an RRA (AVERAGE, MAX etc)
623 0     0 1 0 my ($self, $rraidx) = @_; my $rrd=$self->{rrd};
  0         0  
624 0 0 0     0 if ($rraidx > $rrd->{rra_cnt} || $rraidx<0) {croak("RRA index out of range\n");}
  0         0  
625 0         0 return $rrd->{rra}[$rraidx]->{name};
626             }
627              
628             sub RRA_step {
629             # return the step size (in seconds) used in an RRA
630 2     2 1 4 my ($self, $rraidx) = @_; my $rrd=$self->{rrd};
  2         3  
631 2 50 33     10 if ($rraidx > $rrd->{rra_cnt} || $rraidx<0) {croak("RRA index out of range\n");}
  0         0  
632 2         10 return $rrd->{rra}[$rraidx]->{pdp_cnt}*$rrd->{pdp_step};
633             }
634              
635             sub RRA_xff {
636             # return the xff value for an RRA
637 2     2 1 4 my ($self, $idx) = @_; my $rrd=$self->{rrd};
  2         3  
638 2 50 33     11 if ($idx > $rrd->{rra_cnt} || $idx<0) {croak("RRA index out of range\n");}
  0         0  
639 2         13 return $rrd->{rra}[$idx]->{xff};
640             }
641              
642             sub RRA_el {
643             # fetch a specified element from a specified RRA.
644             # given the index number of the RRA, the index of the DS and the row within the RRA (oldest row is 0),
645             # returns a pair (t,d) where t is the unix timestamp of the data point and d is the data value
646 3     3 1 8 my ($self, $rraidx, $ds_name, $tidx) = @_; my $rrd=$self->{rrd};
  3         5  
647            
648 3 50 33     15 if ($rraidx > $rrd->{rra_cnt} || $rraidx<0) {croak("RRA index out of range\n");}
  0         0  
649 3         6 my $dsidx=$self->_findDSidx($ds_name);
650 3 50 33     13 if ($tidx >= $rrd->{rra}[$rraidx]->{row_cnt} || $tidx<0) {croak("Row index out of range\n");}
  0         0  
651              
652             # load RRA data, if not already loaded
653 3 50       7 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  0         0  
654              
655 3         11 my $t = $rrd->{last_up} - $rrd->{last_up}%($rrd->{rra}[$rraidx]->{pdp_cnt}*$rrd->{pdp_step}) -($rrd->{rra}[$rraidx]->{row_cnt}-1-$tidx)*$rrd->{rra}[$rraidx]->{pdp_cnt}*$rrd->{pdp_step};
656 3         6 my $jj= ($rrd->{rra}[$rraidx]->{ptr}+1+ $tidx)%$rrd->{rra}[$rraidx]->{row_cnt};
657 3         7 my @line=$self->_unpackd($rrd->{rra}[$rraidx]->{data}[$jj]);
658 3         26 return ($t, $line[$dsidx]);
659             }
660              
661             sub set_RRA_el {
662             # change value of a specified element from a specified RRA
663             # given the index number of the RRA, the index of the DS and the row within the RRA (oldest row is 0),
664             # updates the data value to be $val
665 1     1 1 3 my ($self, $rraidx, $ds_name, $tidx, $val) = @_; my $rrd=$self->{rrd};
  1         3  
666 1         5 my $dsidx=$self->_findDSidx($ds_name);
667            
668             # load RRA data, if not already loaded
669 1 50       6 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  1         4  
670              
671 1         7 my $jj= ($rrd->{rra}[$rraidx]->{ptr}+1 + $tidx)%$rrd->{rra}[$rraidx]->{row_cnt};
672 1         16 my @line=$self->_unpackd($rrd->{rra}[$rraidx]->{data}[$jj]);
673 1         4 $line[$dsidx] = $val;
674 1         6 $rrd->{rra}[$rraidx]->{data}[$jj]=$self->_packd(\@line);
675             }
676              
677             sub last {
678             # return time of last update
679 3     3 1 20 return $_[0]->{rrd}->{last_up};
680             }
681              
682             sub set_last {
683             # change time of last update; use with caution !
684 0     0 1 0 $_[0]->{rrd}->{last_up} = $_[1];
685 0         0 return 1;
686             }
687              
688             sub lastupdate {
689             # return the most recent update values
690 3     3 1 7 my $self=$_[0]; my $rrd=$self->{rrd};
  3         6  
691              
692 3         5 my @vals;
693 3         13 for (my $i=0; $i<$rrd->{ds_cnt}; $i++) {
694 12         37 push(@vals,$rrd->{ds}[$i]->{pdp_prep}->{last_ds});
695             }
696 3         17 return @vals;
697             }
698              
699             sub minstep {
700             # return the min step size, in seconds
701 2     2 1 4 my $self=$_[0]; my $rrd=$self->{rrd};
  2         2  
702 2         7 return $rrd->{pdp_step};
703             }
704              
705             sub DS_heartbeat {
706             # return heartbeat for DS
707 2     2 1 4 my ($self, $name) = @_; my $rrd=$self->{rrd};
  2         4  
708            
709 2 50       5 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  2         5  
  0         0  
710 2         17 return $rrd->{ds}[$idx]->{hb};
711             }
712              
713             sub set_DS_heartbeat {
714             # change heartbeat for DS
715 1     1 1 4 my ($self, $name, $hb) = @_; my $rrd=$self->{rrd};
  1         4  
716            
717 1 50       6 if ($hb < $rrd->{pdp_step}) {croak("Heartbeat value must be at least the minimum step size ".$rrd->{pdp_step}." secs\n");}
  0         0  
718            
719 1 50       5 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         10  
  0         0  
720             # update to new value
721 1         4 $rrd->{ds}[$idx]->{hb}=$hb;
722 1         5 return 1;
723             }
724              
725             sub DS_min {
726             # return min value for DS
727 1     1 1 2 my ($self, $name) = @_; my $rrd=$self->{rrd};
  1         3  
728            
729 1 50       3 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         3  
  0         0  
730 1         4 return $rrd->{ds}[$idx]->{min};
731             }
732              
733             sub set_DS_min {
734             # change min value for DS
735 1     1 1 13 my ($self, $name, $min) = @_; my $rrd=$self->{rrd};
  1         4  
736            
737 1 50       5 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         15  
  0         0  
738             # update to new value
739 1         5 $rrd->{ds}[$idx]->{min}=$min;
740 1         7 return 1;
741             }
742              
743             sub DS_max {
744             # return max value for DS
745 1     1 1 2 my ($self, $name) = @_; my $rrd=$self->{rrd};
  1         2  
746            
747 1 50       2 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         5  
  0         0  
748 1         3 return $rrd->{ds}[$idx]->{max};
749             }
750              
751             sub set_DS_max {
752             # change max value for DS
753 1     1 1 3 my ($self, $name, $max) = @_; my $rrd=$self->{rrd};
  1         3  
754            
755 1 50       4 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         5  
  0         0  
756             # update to new value
757 1         5 $rrd->{ds}[$idx]->{max}=$max;
758 1         5 return 1;
759             }
760              
761             sub DS_type {
762             # return type of DS
763 2     2 1 4 my ($self, $name) = @_; my $rrd=$self->{rrd};
  2         3  
764            
765 2 50       4 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  2         6  
  0         0  
766 2         8 return $rrd->{ds}[$idx]->{type};
767             }
768              
769             sub set_DS_type {
770             # change type of DS
771 1     1 1 4 my ($self, $name, $type) = @_; my $rrd=$self->{rrd};
  1         4  
772            
773 1 50       4 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         7  
  0         0  
774 1 50       16 if ($type !~ m/(GAUGE|COUNTER|DERIVE|ABSOLUTE)/) { croak("Invalid DS type\n");}
  0         0  
775             # update to new value
776 1         5 $rrd->{ds}[$idx]->{type}=$type;
777 1         6 return 1;
778             }
779              
780             sub rename_DS {
781 1     1 1 4 my ($self, $old, $new) = @_; my $rrd=$self->{rrd};
  1         3  
782              
783 1 50       5 my $idx=$self->_findDSidx($old); if ($idx<0) {croak("Unknown source\n");}
  1         5  
  0         0  
784 1         3 $rrd->{ds}[$idx]->{name}=$new;
785 1         6 return 1;
786             }
787              
788             sub add_DS {
789             # add a new DS. argument is is same format as used by create
790 1     1 1 3 my ($self, $arg) = @_; my $rrd=$self->{rrd};
  1         4  
791            
792 1 50       48 if ($arg !~ m/^DS:([a-zA-Z0-9_\-]+):(GAUGE|COUNTER|DERIVE|ABSOLUTE):([0-9]+):(U|[-\+]?[0-9\.]+):(U|[-\+]?[0-9\.]+)$/) { croak("Invalid DS spec\n");}
  0         0  
793              
794             # load RRA data, if not already loaded
795 1 50       6 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  1         5  
796              
797             # update DS definitions
798 1         2 my $ds;
799 1 50       5 my $min=$4; if ($min eq "U") {$min=NAN;} # set to NaN
  1         5  
  1         3  
800 1 50       4 my $max=$5; if ($max eq "U") {$max=NAN;} # set to NaN
  1         4  
  1         3  
801 1         22 ($ds->{name}, $ds->{type}, $ds->{hb}, $ds->{min}, $ds->{max},
802             $ds->{pdp_prep}->{last_ds}, $ds->{pdp_prep}->{unkn_sec_cnt}, $ds->{pdp_prep}->{val},
803             )= ($1,$2,$3,$min,$max,"U", $rrd->{last_up}%$rrd->{pdp_step}, 0.0);
804 1         4 $rrd->{ds}[@{$rrd->{ds}}]=$ds;
  1         3  
805 1         3 $rrd->{ds_cnt}++;
806            
807             # update RRAs
808 1         1 my $ii;
809 1         5 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
810 5         23 @{$rrd->{rra}[$ii]->{cdp_prep}[$rrd->{ds_cnt}-1]} = (NAN,(($rrd->{last_up}-$rrd->{last_up}%$rrd->{pdp_step})%($rrd->{pdp_step}*$rrd->{rra}[$ii]->{pdp_cnt}))/$rrd->{pdp_step},0,0,0,0,0,0,0,0);
  5         37  
811             }
812             # update data
813 1         3 my @line; my $i;
814 1         5 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
815 5         21 for ($i=0; $i<$rrd->{rra}[$ii]->{row_cnt}; $i++) {
816 25         64 @line=$self->_unpackd($rrd->{rra}[$ii]->{data}[$i]);
817 25         56 $line[$rrd->{ds_cnt}-1]=NAN;
818 25         55 $rrd->{rra}[$ii]->{data}[$i]=$self->_packd(\@line);
819             }
820             }
821 1         9 return 1;
822             }
823              
824             sub delete_DS {
825             # delete a DS
826 1     1 1 4 my ($self, $name) = @_; my $rrd=$self->{rrd};
  1         3  
827 1 50       5 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         14  
  0         0  
828              
829             # load RRA data, if not already loaded
830 1 50       6 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  0         0  
831              
832             # update DS definitions
833 1         2 my $i;
834 1         3 $rrd->{ds_cnt}--;
835 1         6 for ($i=$idx; $i<$rrd->{ds_cnt}; $i++) {
836 2         13 $rrd->{ds}[$i]=$rrd->{ds}[$i+1];
837             }
838            
839             # update RRAs
840 1         2 my $ii;
841 1         6 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
842 5         12 for ($i=$idx; $i<$rrd->{ds_cnt}; $i++) {
843 10         50 $rrd->{rra}[$ii]->{cdp_prep}[$i]=$rrd->{rra}[$ii]->{cdp_prep}[$i+1];
844             }
845             }
846              
847             # update data
848 1         2 my $j; my @line;
849 1         6 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
850 5         13 for ($i=0; $i<$rrd->{rra}[$ii]->{row_cnt}; $i++) {
851 25         133 @line=$self->_unpackd($rrd->{rra}[$ii]->{data}[$i]);
852 25         77 for ($j=$idx; $j<$rrd->{ds_cnt}; $j++) {
853 50         122 $line[$j]=$line[$j+1];
854             }
855 25         110 $rrd->{rra}[$ii]->{data}[$i]=$self->_packd([@line[0..$rrd->{ds_cnt}-1]]);
856             }
857             }
858 1         8 return 1;
859             }
860              
861             sub add_RRA {
862             # add a new RRA
863 1     1 1 3 my ($self, $args) = @_; my $rrd=$self->{rrd};
  1         3  
864 1 50       12 if ($args !~ m/^RRA:(AVERAGE|MAX|MIN|LAST):([0-9\.]+):([0-9]+):([0-9]+)$/) {croak("Invalid RRA spec\n");}
  0         0  
865             # load RRA data, if not already loaded
866 1 50       6 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  0         0  
867             # update RRA definitions
868 1         2 my $rra;
869 1 50       8 if ($4<1) { croak("Invalid row count $4\n");}
  0         0  
870 1 50 33     12 if ($2<0.0 || $2>1.0) { croak("Invalid xff $2: must be between 0 and 1\n");}
  0         0  
871 1 50       4 if ($3<1) { croak("Invalid step $3: must be >= 1\n");}
  0         0  
872 1         57 ($rra->{name}, $rra->{xff}, $rra->{pdp_cnt}, $rra->{row_cnt}, $rra->{ptr}, $rra->{data})=($1,$2,$3,$4,int(rand($4)),undef);
873 1         4 my $idx=@{$rrd->{rra}};
  1         3  
874 1         4 $rrd->{rra}[$idx]=$rra;
875 1         3 $rrd->{rra_cnt}++;
876            
877 1         1 my $i;
878 1         7 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
879 4         17 @{$rrd->{rra}[$idx]->{cdp_prep}[$i]} = (NAN,(($rrd->{last_up}-$rrd->{last_up}%$rrd->{pdp_step})%($rrd->{pdp_step}*$rrd->{rra}[$idx]->{pdp_cnt}))/$rrd->{pdp_step},0,0,0,0,0,0,0,0);
  4         32  
880             }
881             # update data
882 1         4 my @empty=((NAN)x$rrd->{ds_cnt});
883 1         12 for ($i=0; $i<$rrd->{rra}[$idx]->{row_cnt}; $i++) {
884 10         23 $rrd->{rra}[$idx]->{data}[$i] = $self->_packd(\@empty);
885             }
886 1         7 return 1;
887             }
888              
889             sub delete_RRA {
890             # delete an RRA
891 1     1 1 2 my ($self, $idx) = @_; my $rrd=$self->{rrd};
  1         3  
892 1 50 33     12 if ($idx > $rrd->{rra_cnt} || $idx<0) {croak("RRA index out of range\n");}
  0         0  
893             # load RRA data, if not already loaded
894 1 50       5 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  0         0  
895             # update RRA
896 1         4 $rrd->{rra_cnt}--;
897 1         5 for (my $i=$idx; $i<$rrd->{rra_cnt}; $i++) {
898 5         27 $rrd->{rra}[$i]=$rrd->{rra}[$i+1];
899             }
900 1         5 return 1;
901             }
902              
903             sub resize_RRA {
904 1     1 1 3 my ($self, $idx, $size) = @_; my $rrd=$self->{rrd};
  1         3  
905            
906 1 50 33     11 if ($idx > $rrd->{rra_cnt} || $idx<0) {croak("RRA index out of range\n");}
  0         0  
907 1 50       4 if ($size < 0) {$size=0;}
  0         0  
908             # load RRA data, if not already loaded
909 1 50       5 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  0         0  
910             # update data
911 1         6 my @empty=((NAN)x$rrd->{ds_cnt});
912 1         10 for (my $i=$rrd->{rra}[$idx]->{row_cnt}; $i<$size; $i++) {
913 15         26 $rrd->{rra}[$idx]->{data}[$i] = $self->_packd(\@empty);
914             }
915 1         4 $rrd->{rra}[$idx]->{row_cnt} = $size;
916 1         6 return 1;
917             }
918              
919             sub set_RRA_xff {
920             # schange xff value for an RRA
921 1     1 1 8 my ($self, $idx, $xff) = @_; my $rrd=$self->{rrd};
  1         4  
922 1 50 33     10 if ($idx > $rrd->{rra_cnt} || $idx<0) {croak("RRA index out of range\n");}
  0         0  
923 1         4 $rrd->{rra}[$idx]->{xff}=$xff;
924 1         6 return 1;
925             }
926              
927             #sub set_RRA_step {
928             # TODO: change RRA step size - will require resampling
929             #}
930              
931             sub update {
932             # a re-implementation of rrdupdate. updates file in place on disk, if possible - much faster.
933            
934 10     10 1 27 my ($self, $args_str) = @_; my $rrd=$self->{rrd};
  10         20  
935            
936 10         12 my $ret; my $args; my $template='';
  10         13  
937 10         24 ($ret, $args) = _GetOptionsFromString($args_str,
938             "template|t:s" => \$template,
939             );
940              
941             # update file in place ?
942 10         14 my $inplace; my $fd;
943 10 50       27 if (defined($rrd->{dataloaded})) {
944 10         17 $inplace="memory"; # data is already loaded into memory so do update there. will need to subsequently call save() to write data to disk
945             } else {
946 0 0       0 if (defined($self->{fd})) {
947 0         0 $inplace="file"; # data is not loaded yet, so carry out update in place in file. more efficient - no need to call save() to write data to disk.
948 0 0       0 open $self->{fd}, "+<", $self->{file_name} or croak "Couldn't open file ".$self->{file_name}.": $!\n"; # reopen file in update mode ($self->open() opens in read-only mode)
949 0         0 binmode($self->{fd});
950 0         0 $fd=$self->{fd};
951             } else {
952 0         0 croak("update: must call open() or create() first\n");
953             }
954             }
955              
956             # Parse template, if provided
957 10         10 my $i; my $j;
958 10         23 my @tmp=split(/:/,$template);
959 10         14 my @idx;
960 10 100       18 if (@tmp == 0) {
961             # no template, default to complete DS list
962 8         21 @idx=(0 .. $rrd->{ds_cnt}-1);
963             } else {
964             # read DS list from template
965 2         13 for ($i=0; $i<@tmp; $i++) {
966 7 50       19 $idx[$i]=$self->_findDSidx($tmp[$i]); if($idx[$i]<0) {croak("Unknown DS name ".$tmp[$i]."\n");}
  7         27  
  0         0  
967             }
968             }
969             # Parse update strings - updates the primary data points (PDPs)
970             # and consolidated data points (CDPs), and writes changes to the RRAs.
971 10         13 my @updvals; my @bits; my $rate; my $current_time; my $interval;
  0         0  
  0         0  
  0         0  
972 10         16 for ($i=0; $i<@{$args}; $i++) {
  20         54  
973             #parse colon-separated DS string
974 10 50       33 if ($args->[$i] =~ m/(-t|--template)/) {next;} # ignore option here
  0         0  
975 10 50       24 if ($args->[$i] =~ m/\@/) {croak("\@ time format not supported - use either N or a unix timestamp\n");}
  0         0  
976 10         59 @bits=split(/:/,$args->[$i]);
977 10 50       26 if (@bits-1 < @idx) {croak("expected ".@idx." data source readings (got ".(@bits-1).") from ".$args->[$i],"\n");}
  0         0  
978             #get_time_from_reading
979 10 50       22 if ($bits[0] eq "N") {
980 0         0 $current_time=time();
981             #normalize_time
982             } else {
983 10         13 $current_time=$bits[0];
984             }
985 10 50       28 if ($current_time < $rrd->{last_up}) {croak("attempt to update using time $current_time when last update time is ". $rrd->{last_up}."\n");}
  0         0  
986 10         16 $interval=$current_time - $rrd->{last_up};
987             # initialise values to NaN
988 10         26 for ($j=0; $j<$rrd->{ds_cnt}; $j++) {
989 40         69 $updvals[$j]="U";
990             }
991 10         21 for ($j=0; $j<@idx; $j++) {
992 39         76 $updvals[$idx[$j]] = $bits[$j+1];
993             }
994             # process the data sources and update the pdp_prep area accordingly
995 10         13 my @pdp_new=();
996 10         20 for ($j=0;$j<@updvals; $j++) {
997 40 50       114 if ($rrd->{ds}[$j]->{hb} < $interval) {
998             # make sure we do not build diffs with old last_ds values
999 0         0 $rrd->{ds}[$j]->{pdp_prep}->{last_ds}="U";
1000             }
1001 40 100 66     154 if ($updvals[$j] ne "U" && $rrd->{ds}[$j]->{hb} >= $interval) {
1002 39         41 $rate=NAN;
1003 39 100       112 if ( $rrd->{ds}[$j]->{type} eq "COUNTER" ) {
    100          
    100          
1004 10 50       48 if ($updvals[$j] !~ m/^\d+$/) {croak("not a simple unsigned integer ".$updvals[$j]);}
  0         0  
1005 10 100       31 if ($rrd->{ds}[$j]->{pdp_prep}->{last_ds} ne "U") {
1006             #use bignum; # need this for next line as might be large integers
1007 9         28 $pdp_new[$j] = $updvals[$j] - $rrd->{ds}[$j]->{pdp_prep}->{last_ds};
1008             # simple overflow catcher
1009 9 100       17 if ($pdp_new[$j] < 0) {$pdp_new[$j]+=4294967296; } #2^32
  3         4  
1010 9 50       23 if ($pdp_new[$j] < 0) {$pdp_new[$j]+=18446744069414584320; } #2^64-2^32
  0         0  
1011 9         18 $rate=$pdp_new[$j]/$interval;
1012             } else {
1013 1         2 $pdp_new[$j]=NAN;
1014             }
1015             } elsif ( $rrd->{ds}[$j]->{type} eq "DERIVE" ) {
1016 9 50       39 if ($updvals[$j] !~ m/^[+|-]?\d+$/) {croak("not a simple signed integer ".$updvals[$j]);}
  0         0  
1017 9 100       24 if ($rrd->{ds}[$j]->{pdp_prep}->{last_ds} ne "U") {
1018             #use bignum; # need this for next line as might be large integers
1019 8         21 $pdp_new[$j] = $updvals[$j] - $rrd->{ds}[$j]->{pdp_prep}->{last_ds};
1020 8         10 $rate=$pdp_new[$j]/$interval;
1021             } else {
1022 1         2 $pdp_new[$j]=NAN;
1023             }
1024             } elsif ( $rrd->{ds}[$j]->{type} eq "GAUGE" ) {
1025 10 50       54 if ($updvals[$j] !~ m/^(-)?[\d]+(\.[\d]+)?$/) {croak("not a number ".$updvals[$j]);}
  0         0  
1026 10         35 $pdp_new[$j] = $updvals[$j]*$interval;
1027 10         15 $rate=$pdp_new[$j]/$interval;
1028             } else { # ABSOLUTE
1029 10         15 $pdp_new[$j] = $updvals[$j];
1030 10         17 $rate=$pdp_new[$j]/$interval;
1031             }
1032 39 50 33     65 if (!_isNan($rate)
      66        
1033             && (
1034             (!_isNan($rrd->{ds}[$j]->{max}) && $rate >$rrd->{ds}[$j]->{max})
1035             || (!_isNan($rrd->{ds}[$j]->{min}) && $rate <$rrd->{ds}[$j]->{min})
1036             )) {
1037 0         0 $pdp_new[$j]=NAN;
1038             }
1039             } else {
1040 1         4 $pdp_new[$j]=NAN;
1041             }
1042 40         135 $rrd->{ds}[$j]->{pdp_prep}->{last_ds} = $updvals[$j];
1043             }
1044             # how many PDP steps have elapsed since the last update?
1045 10         24 my $proc_pdp_st = $rrd->{last_up} - $rrd->{last_up} % $rrd->{pdp_step};
1046 10         12 my $occu_pdp_age = $current_time % $rrd->{pdp_step};
1047 10         13 my $occu_pdp_st = $current_time - $occu_pdp_age;
1048 10         10 my $pre_int; my $post_int;
1049 10 50       48 if ($occu_pdp_st > $proc_pdp_st) {
1050             # OK we passed the pdp_st moment
1051 10         12 $pre_int = $occu_pdp_st - $rrd->{last_up};
1052 10         10 $post_int = $occu_pdp_age;
1053             } else {
1054 0         0 $pre_int = $interval;
1055 0         0 $post_int=0;
1056             }
1057 10         25 my $proc_pdp_cnt = int( $proc_pdp_st / $rrd->{pdp_step} );
1058 10         16 my $elapsed_pdp_st = int( ($occu_pdp_st - $proc_pdp_st)/$rrd->{pdp_step} );
1059             # have we moved past a pdp step size since last run ?
1060 10 50       17 if ($elapsed_pdp_st == 0) {
1061             # nope, simple_update
1062 0         0 for ($j=0; $j<$rrd->{ds_cnt}; $j++) {
1063 0 0       0 if (_isNan($pdp_new[$j])) {
    0          
1064 0         0 $rrd->{ds}[$j]->{pdp_prep}->{unkn_sec_cnt} += int($interval);
1065             } elsif (_isNan($rrd->{ds}[$j]->{pdp_prep}->{val}) ) {
1066 0         0 $rrd->{ds}[$j]->{pdp_prep}->{val} = $pdp_new[$j];
1067             } else {
1068 0         0 $rrd->{ds}[$j]->{pdp_prep}->{val} += $pdp_new[$j];
1069             }
1070             }
1071             } else {
1072             # yep
1073             # process_all_pdp_st
1074 10         12 my $pre_unknown; my @pdp_temp; my $diff_pdp_st;
  0         0  
1075 10         25 for ($j=0; $j<$rrd->{ds_cnt}; $j++) {
1076             # Process an update that occurs after one of the PDP moments.
1077             # Increments the PDP value, sets NAN if time greater than the heartbeats have elapsed
1078 40         38 $pre_unknown=0;
1079 40 100       64 if (_isNan($pdp_new[$j])) {
1080 3         4 $pre_unknown=$pre_int;
1081             } else {
1082             #print $rrd->{ds}[$j]->{pdp_prep}->{val}," ";
1083 37 100       66 if (_isNan($rrd->{ds}[$j]->{pdp_prep}->{val})) {
1084 2         4 $rrd->{ds}[$j]->{pdp_prep}->{val} = 0;
1085             }
1086 37         98 $rrd->{ds}[$j]->{pdp_prep}->{val} += $pdp_new[$j]/$interval * $pre_int;
1087             }
1088             #print $pdp_new[$j]," ",$interval," ",$pre_int," ",$rrd->{ds}[$j]->{pdp_prep}->{val},"\n";
1089 40 100 66     182 if ($interval > $rrd->{ds}[$j]->{hb} || $rrd->{pdp_step}/2.0 < $rrd->{ds}[$j]->{pdp_prep}->{unkn_sec_cnt}+$pre_unknown) {
1090 5         8 $pdp_temp[$j]=NAN;
1091             } else {
1092 35         77 $pdp_temp[$j]=$rrd->{ds}[$j]->{pdp_prep}->{val}/($elapsed_pdp_st*$rrd->{pdp_step}-$rrd->{ds}[$j]->{pdp_prep}->{unkn_sec_cnt}-$pre_unknown);
1093             }
1094             #print $pdp_new[$j]," ",$pdp_temp[$j]," ",$rrd->{ds}[$j]->{pdp_prep}->{val}," ", $elapsed_pdp_st-$rrd->{ds}[$j]->{pdp_prep}->{unkn_sec_cnt}-$pre_unknown,"\n";
1095 40 100       54 if (_isNan($pdp_new[$j])) {
1096 3         9 $rrd->{ds}[$j]->{pdp_prep}->{unkn_sec_cnt} = int($post_int);
1097 3         12 $rrd->{ds}[$j]->{pdp_prep}->{val}=NAN;
1098             } else {
1099 37         51 $rrd->{ds}[$j]->{pdp_prep}->{unkn_sec_cnt} = 0;
1100 37         106 $rrd->{ds}[$j]->{pdp_prep}->{val}=$pdp_new[$j]/$interval*$post_int;
1101             #print $pdp_new[$j]," ", $interval, " ", $post_int, " ",$rrd->{ds}[$j]->{pdp_prep}->{val},"\n";
1102             }
1103             }
1104             # update_all_cdp_prep. Iterate over all the RRAs for a given DS and update the CDP
1105 10         11 my $current_cf; my $start_pdp_offset; my @rra_step_cnt;
  0         0  
1106 0         0 my $cum_val; my $cur_val; my $pdp_into_cdp_cnt; my $ii;
  0         0  
  0         0  
  0         0  
1107 10         27 my $idx=$self->_get_header_size; # file position (used by in place updates)
1108 10         29 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
1109 50         96 $start_pdp_offset = $rrd->{rra}[$ii]->{pdp_cnt} - $proc_pdp_cnt % $rrd->{rra}[$ii]->{pdp_cnt};
1110 50 100       69 if ($start_pdp_offset <= $elapsed_pdp_st) {
1111 30         62 $rra_step_cnt[$ii] = int(($elapsed_pdp_st - $start_pdp_offset)/$rrd->{rra}[$ii]->{pdp_cnt}) + 1;
1112             } else {
1113 20         27 $rra_step_cnt[$ii] = 0;
1114             }
1115             # update_cdp_prep. update CDP_PREP areas, loop over data sources within each RRA
1116 50         89 for ($j=0; $j<$rrd->{ds_cnt}; $j++) {
1117 200 100       284 if ($rrd->{rra}[$ii]->{pdp_cnt} > 1) {
1118             # update_cdp. Given the new reading (pdp_temp_val), update or initialize the CDP value, primary value, secondary value, and # of unknowns.
1119 160 100       193 if ($rra_step_cnt[$ii]>0) {
1120 80 100       98 if (_isNan($pdp_temp[$j])) {
1121 16         20 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] +=$start_pdp_offset;
1122 16         20 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[SECONDARY_VAL] = NAN;
1123             } else {
1124 64         97 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[SECONDARY_VAL] = $pdp_temp[$j];
1125             }
1126 80 100       270 if ($rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] > $rrd->{rra}[$ii]->{pdp_cnt}*$rrd->{rra}[$ii]->{xff}) {
1127 16         21 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = NAN;
1128             } else {
1129             #initialize_cdp_val
1130 64 100       154 if ($rrd->{rra}[$ii]->{name} eq "AVERAGE") {
    100          
    100          
1131 16 50       34 if (_isNan($rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL])) {
1132 0         0 $cum_val=0.0;
1133             } else {
1134 16         29 $cum_val = $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL];
1135             }
1136 16 50       28 if (_isNan($pdp_temp[$j])) {
1137 0         0 $cur_val=0.0;
1138             } else {
1139 16         21 $cur_val = $pdp_temp[$j];
1140             }
1141 16         56 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = ($cum_val+$cur_val*$start_pdp_offset)/($rrd->{rra}[$ii]->{pdp_cnt}-$rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT]);
1142             } elsif ($rrd->{rra}[$ii]->{name} eq "MAX") {
1143 16 50       56 if (_isNan($rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL])) {
1144 0         0 $cum_val=-1 * INF;
1145             } else {
1146 16         24 $cum_val = $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL];
1147             }
1148 16 50       26 if (_isNan($pdp_temp[$j])) {
1149 0         0 $cur_val=-1 * INF;
1150             } else {
1151 16         14 $cur_val = $pdp_temp[$j];
1152             }
1153 16 100       23 if ($cur_val > $cum_val) {
1154 8         14 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $cur_val;
1155             } else {
1156 8         17 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $cum_val;
1157             }
1158             } elsif ($rrd->{rra}[$ii]->{name} eq "MIN") {
1159 16 50       30 if (_isNan($rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL])) {
1160 0         0 $cum_val=INF;
1161             } else {
1162 16         28 $cum_val = $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL];
1163             }
1164 16 50       28 if (_isNan($pdp_temp[$j])) {
1165 0         0 $cur_val=INF;
1166             } else {
1167 16         19 $cur_val = $pdp_temp[$j];
1168             }
1169 16 100       30 if ($cur_val < $cum_val) {
1170 8         18 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $cur_val;
1171             } else {
1172 8         16 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $cum_val;
1173             }
1174             } else {
1175 16         28 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $pdp_temp[$j];
1176             }
1177             }
1178             #*cdp_val = initialize_carry_over
1179 80         103 $pdp_into_cdp_cnt=($elapsed_pdp_st - $start_pdp_offset) % $rrd->{rra}[$ii]->{pdp_cnt};
1180 80 50 33     146 if ($pdp_into_cdp_cnt == 0 || _isNan($pdp_temp[$j])) {
1181 80 100       188 if ($rrd->{rra}[$ii]->{name} eq "MAX") {
    100          
    100          
1182 20         29 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=-1 * INF;
1183             } elsif ($rrd->{rra}[$ii]->{name} eq "MIN") {
1184 20         32 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=INF;
1185             } elsif ($rrd->{rra}[$ii]->{name} eq "AVERAGE") {
1186 20         30 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=0;
1187             } else {
1188 20         30 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=NAN;
1189             }
1190             } else {
1191 0 0       0 if ($rrd->{rra}[$ii]->{name} eq "AVERAGE") {
1192 0         0 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=$pdp_temp[$j]*$pdp_into_cdp_cnt;
1193             } else {
1194 0         0 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=$pdp_temp[$j];
1195             }
1196             }
1197 80 100       100 if (_isNan($pdp_temp[$j])) {
1198 16         48 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] = ($elapsed_pdp_st - $start_pdp_offset) % $rrd->{rra}[$ii]->{pdp_cnt};
1199             } else {
1200 64         179 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] = 0;
1201             }
1202             } else {
1203 80 100       109 if (_isNan($pdp_temp[$j])) {
1204 4         18 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] += $elapsed_pdp_st;
1205             } else {
1206             #*cdp_val =calculate_cdp_val
1207 76 100       150 if (_isNan($rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL])) {
1208 19 50       40 if ($rrd->{rra}[$ii]->{name} eq "AVERAGE") {
1209 0         0 $pdp_temp[$j] *= $elapsed_pdp_st;
1210             }
1211 19         49 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=$pdp_temp[$j];
1212             } else {
1213 57 100       160 if ($rrd->{rra}[$ii]->{name} eq "AVERAGE") {
    100          
    50          
1214 19         64 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]+=$pdp_temp[$j]*$elapsed_pdp_st;
1215             } elsif ($rrd->{rra}[$ii]->{name} eq "MIN") {
1216 19 50       49 if ($pdp_temp[$j] < $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]) {
1217 19         95 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL] = $pdp_temp[$j];
1218             }
1219             } elsif ($rrd->{rra}[$ii]->{name} eq "MAX") {
1220 19 50       48 if ($pdp_temp[$j] > $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]) {
1221 19         53 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL] = $pdp_temp[$j];
1222             }
1223             } else {
1224 0         0 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL] = $pdp_temp[$j];
1225             }
1226             }
1227             }
1228             }
1229             } else {
1230             # Nothing to consolidate if there's one PDP per CDP
1231 40         59 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $pdp_temp[$j];
1232 40 50       95 if ($elapsed_pdp_st > 1) {
1233 0         0 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[SECONDARY_VAL] = $pdp_temp[$j];
1234             }
1235             # consolidated with update_aberrant_cdps
1236             }
1237             } # $j ds_cnt
1238             # write to RRA
1239 50         98 for (my $scratch_idx=PRIMARY_VAL; $rra_step_cnt[$ii] >0; $rra_step_cnt[$ii]--, $scratch_idx=SECONDARY_VAL) {
1240 30         73 $rrd->{rra}[$ii]->{ptr} = ($rrd->{rra}[$ii]->{ptr}+1) % $rrd->{rra}[$ii]->{row_cnt};
1241             #write_RRA_row
1242 30         29 my @line;
1243 30         52 for ($j=0; $j<$rrd->{ds_cnt}; $j++) {
1244 120         261 push(@line, $rrd->{rra}[$ii]->{cdp_prep}[$j]->[$scratch_idx]);
1245             }
1246 30 50       44 if ($inplace eq "memory") {
1247 30         79 $rrd->{rra}[$ii]->{data}[$rrd->{rra}[$ii]->{ptr}] = $self->_packd(\@line);
1248             } else {
1249             # update in place
1250 0         0 seek $fd,$idx+$rrd->{rra}[$ii]->{ptr}*$rrd->{ds_cnt}*$self->{FLOAT_EL_SIZE},0;
1251 0         0 print $fd $self->_packd(\@line);
1252             }
1253             # rrd_notify_row
1254             }
1255 50         164 $idx+=$rrd->{rra}[$ii]->{row_cnt}*$rrd->{ds_cnt}*$self->{FLOAT_EL_SIZE}; # step file pointer to start of next RRA
1256             } # $ii rra_cnt
1257             } # complex update
1258 10         29 $rrd->{last_up}=$current_time;
1259             } # args
1260 10 50       22 if ($inplace eq "file") {
1261             # update header
1262 0         0 seek $fd,0,0;
1263             #print $fd $self->getheader();
1264 0         0 $self->_saveheader($fd);
1265             #close($fd);
1266             }
1267 10         68 return 1;
1268             }
1269              
1270             sub fetch {
1271             # dump out measurement data
1272 1     1 1 542 my ($self, $args_str) = @_; my $rrd=$self->{rrd};
  1         3  
1273 1         2 my $out='';
1274            
1275 1         2 my $step=$rrd->{pdp_step}; my $start=time()-24*60*60; my $end=time(); my $digits=10; # number of digits printed for floats
  1         5  
  1         3  
  1         1  
1276 1         2 my $ret; my $args;
1277 1         4 ($ret, $args) = _GetOptionsFromString($args_str,
1278             "resolution|r:i" => \$step,
1279             "start|s:i" => \$start,
1280             "end|e:i" => \$end,
1281             "digits|d:i" => \$digits
1282             );
1283             # at the moment, start/end times are unix timestamps.
1284 1 50       5 if ($start < 3600 * 24 * 365 * 10) {croak("the first entry to fetch should be after 1980");}
  0         0  
1285 1 50       4 if ($end < $start) {croak("start ($start) should be less than end ($end)");}
  0         0  
1286 1 50       4 if ($step<1) {croak("step must be >= 1 second");}
  0         0  
1287 1         3 my $cf=uc($args->[0]); my $i; # so CF must be first word in argument line
  1         1  
1288 1 50       8 if ($cf !~ m/AVERAGE|MIN|MAX|LAST/) {croak("unknown CF\n");}
  0         0  
1289            
1290             # find the RRA which best matches the requirements
1291 1         1 my $cal_end; my $cal_start; my $step_diff; my $firstfull=1; my $firstpart=1;
  0         0  
  1         2  
  1         1  
1292 1         2 my $full_match=$end-$start;
1293 1         2 my $best_full_step_diff=0; my $best_full_rra; my $best_match=0;
  1         1  
  1         1  
1294 1         1 my $best_part_step_diff=0; my $best_part_rra;
  1         1  
1295             my $tmp_match;
1296 1         4 for ($i = 0; $i < $rrd->{rra_cnt}; $i++) {
1297 5 100       13 if ($rrd->{rra}[$i]->{name} eq $cf) {
1298 2         6 $cal_end=$rrd->{last_up} - $rrd->{last_up}%($rrd->{rra}[$i]->{pdp_cnt}*$rrd->{pdp_step});
1299 2         5 $cal_start=$cal_end - $rrd->{rra}[$i]->{pdp_cnt}*$rrd->{rra}[$i]->{row_cnt}*$rrd->{pdp_step};
1300 2         5 $step_diff = $step-$rrd->{pdp_step}*$rrd->{rra}[$i]->{pdp_cnt};
1301 2 100       5 if ($step_diff<0) {$step_diff=-$step_diff;} # take absolute value
  1         2  
1302 2 100       6 if ($cal_start <= $start) {
1303 1 50 33     4 if ($firstfull || $step_diff < $best_full_step_diff) {
1304 1         1 $firstfull=0; $best_full_step_diff = $step_diff; $best_full_rra=$i;
  1         2  
  1         2  
1305             }
1306             } else {
1307 1         1 $tmp_match = $full_match;
1308 1 50       3 if ($cal_start>$start) {$tmp_match-=($cal_start-$start);}
  1         2  
1309 1 50 0     4 if ($firstpart || ($best_match<$tmp_match && $step_diff < $best_part_step_diff)) {
      33        
1310 1         1 $firstpart=0; $best_match=$tmp_match; $best_part_step_diff=$step_diff; $best_part_rra=$i;
  1         13  
  1         2  
  1         3  
1311             }
1312             }
1313             }
1314             }
1315 1         2 my $chosen_rra; my @line;
1316 1 50       6 if ($firstfull == 0) {$chosen_rra=$best_full_rra;}
  1 0       1  
  0         0  
1317 0         0 elsif ($firstpart==0) {$chosen_rra=$best_part_rra;}
1318             else {croak("the RRD does not contain an RRA matching the chosen CF");}
1319 1         3 $step = $rrd->{rra}[$chosen_rra]->{pdp_cnt}*$rrd->{pdp_step};
1320 1         2 $start -= $start % $step;
1321 1         2 $end += ($step - $end % $step);
1322              
1323             # load RRA data, if not already loaded
1324 1 50       3 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  0         0  
1325              
1326             # output column headings
1327 1         3 $out.=sprintf "%12s"," ";
1328 1         4 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
1329 4         13 $out.=sprintf "%-17s", $rrd->{ds}[$i]->{name};
1330             }
1331 1         2 $out.=sprintf "%s", "\n";
1332 1         5 my $t = $rrd->{last_up} - $rrd->{last_up}%($rrd->{rra}[$chosen_rra]->{pdp_cnt}*$rrd->{pdp_step}) -($rrd->{rra}[$chosen_rra]->{row_cnt}-1)*$rrd->{rra}[$chosen_rra]->{pdp_cnt}*$rrd->{pdp_step};
1333 1         1 my $jj; my $j;
1334 1         5 for ($j=0; $j<$rrd->{rra}[$chosen_rra]->{row_cnt}; $j++) {
1335 5 50 33     19 if ($t > $start && $t <= $end+$step) {
1336 5         11 $out.=sprintf "%10u: ",$t;
1337 5         9 $jj= ($rrd->{rra}[$chosen_rra]->{ptr}+1 + $j)%$rrd->{rra}[$chosen_rra]->{row_cnt};
1338 5         12 @line=$self->_unpackd($rrd->{rra}[$chosen_rra]->{data}[$jj]);
1339 5         10 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
1340 20         26 $out.=sprintf "%-17s",_strfloat($line[$i],$digits);
1341             }
1342 5         6 $out.=sprintf "%s", "\n";
1343             }
1344 5         11 $t+=$step;
1345             }
1346 1         37 return $out;
1347             }
1348              
1349             sub info {
1350             # dump out header info
1351 1     1 1 2 my $self=$_[0]; my $rrd = $self->{rrd};
  1         2  
1352 1         2 my $out='';
1353            
1354 1         2 my $digits=10; my $noencoding=0;
  1         1  
1355 1 50       4 if (defined($_[1])) {
1356 1         1 my $ret; my $args;
1357 1         5 ($ret, $args) = _GetOptionsFromString($_[1],
1358             "digits|d:i" => \$digits,
1359             "noformat|n" => \$noencoding
1360             );
1361             }
1362            
1363 1         7 $out.=sprintf "%s", "rrd_version = ".$rrd->{version}."\n";
1364 1 50       5 if ($noencoding<0.5) {
1365 0         0 $out.=sprintf "%s", "encoding = ";
1366 0 0 0     0 if ($self->{encoding} eq "native-double-simple" || $self->{encoding} eq "native-double-mixed") {
    0          
1367 0         0 $out.="native-double";
1368             } elsif ($self->{encoding} =~ /double/) {
1369 0         0 $out.="portable-double";
1370             } else {
1371 0         0 $out.="portable-single";
1372             }
1373 0         0 $out.=" (".$self->{encoding}.")\n";
1374             }
1375 1         3 $out.=sprintf "%s", "step = ".$rrd->{pdp_step}."\n";
1376 1         5 $out.=sprintf "%s", "last_update = ".int($rrd->{last_up})."\n";
1377 1         1 my $i; my $str; my $ii;
  0         0  
1378 1         3 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
1379 4         10 $str="ds[".$rrd->{ds}[$i]->{name}."]";
1380 4         21 $out.=sprintf "%s", "$str.index = ".$i."\n";
1381 4         10 $out.=sprintf "%s", "$str.type = \"".$rrd->{ds}[$i]->{type}."\"\n";
1382 4         10 $out.=sprintf "%s", "$str.minimal_heartbeat = ".$rrd->{ds}[$i]->{hb}."\n";
1383 4         10 $out.=sprintf "%s.min = %s\n",$str,_strint($rrd->{ds}[$i]->{min});
1384 4         10 $out.=sprintf "%s.max = %s\n",$str,_strint($rrd->{ds}[$i]->{max});
1385 4         12 $out.=sprintf "%s", "$str.last_ds = \"".$rrd->{ds}[$i]->{pdp_prep}->{last_ds}."\"\n";
1386 4         8 $out.=sprintf "%s.value = %s\n",$str,_strfloat($rrd->{ds}[$i]->{pdp_prep}->{val}, $digits);
1387 4         20 $out.=sprintf "%s", "$str.unknown_sec = ".$rrd->{ds}[$i]->{pdp_prep}->{unkn_sec_cnt}."\n";
1388             }
1389 1         5 for ($i=0; $i<$rrd->{rra_cnt}; $i++) {
1390 5         6 $str="rra[$i]";
1391 5         10 $out.=sprintf "%s", "$str.cf = \"".$rrd->{rra}[$i]->{name}."\"\n";
1392 5         13 $out.=sprintf "%s", "$str.rows = ".$rrd->{rra}[$i]->{row_cnt}."\n";
1393 5         7 $out.=sprintf "%s", "$str.cur_row = ".$rrd->{rra}[$i]->{ptr}."\n";
1394 5         12 $out.=sprintf "%s", "$str.pdp_per_row = ".$rrd->{rra}[$i]->{pdp_cnt}."\n";
1395 5         7 $out.=sprintf "%s.xff = %s\n",$str,_strfloat($rrd->{rra}[$i]->{xff},$digits);
1396 5         16 for ($ii=0; $ii<$rrd->{ds_cnt}; $ii++) {
1397 20         39 $out.=sprintf "%s.cdp_prep[$ii].value = %s\n",$str,_strfloat($rrd->{rra}[$i]->{cdp_prep}[$ii]->[VAL],$digits);
1398 20         86 $out.=sprintf "%s", "$str.cdp_prep[$ii].unknown_datapoints = ".$rrd->{rra}[$i]->{cdp_prep}[$ii]->[UNKN_PDP_CNT]."\n";
1399             }
1400             }
1401 1         51 return $out;
1402             }
1403              
1404             #sub xport {
1405             # # TO DO, incl JSON format
1406             # my ($self, $args_str) = @_; my $rrd=$self->{rrd};
1407             #}
1408              
1409             sub dump {
1410             # XML dump of RRD file
1411 4     4 1 11 my ($self, $args_str) = @_; my $rrd=$self->{rrd};
  4         11  
1412              
1413 4         8 my $noheader=0; my $notimecomments=0; my $digits=10;
  4         6  
  4         6  
1414 4 50       20 if (defined($args_str)) {
1415 4         8 my $ret; my $args;
1416 4         19 ($ret, $args) = _GetOptionsFromString($args_str,
1417             "no-header|n" => \$noheader,
1418             "notimecomments|t" => \$notimecomments,
1419             "digits|d:i" => \$digits
1420             );
1421             }
1422 4 50       18 my $timecomments = $notimecomments>0 ? 0 : 1;
1423            
1424             # load RRA data, if not already loaded
1425 4 100       21 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  1         5  
1426              
1427 4         6 my $out=''; my @line;
  4         7  
1428            
1429 4 50       13 if ($noheader<1) {
1430 4         9 $out.=sprintf "%s", ''."\n";
1431 4         10 $out.=sprintf "%s", ''."\n";
1432             }
1433 4         20 $out.=sprintf "%s", "\n\n\t".$rrd->{version}."\n";
1434 4         31 $out.=sprintf "%s", "\t".$rrd->{pdp_step}." \n\t".$rrd->{last_up}."";
1435 4 50       16 if ($timecomments) {$out.=" ";}
  0         0  
1436 4         8 $out.="\n\t";
1437 4         6 my $i; my $ii; my $j; my $jj; my $t; my $val;
  0         0  
  0         0  
  0         0  
  0         0  
1438 4         19 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
1439 16         49 $out.=sprintf "%s", "\n\t\n\t\t".$rrd->{ds}[$i]->{name}."\n\t\t";
1440 16         38 $out.=sprintf "%s", "".$rrd->{ds}[$i]->{type}."\n\t\t";
1441 16         50 $out.=sprintf "%s", "".$rrd->{ds}[$i]->{hb}."\n\t\t";
1442 16         41 $out.=sprintf "%s\n\t\t%s\n\t\t",_strint($rrd->{ds}[$i]->{min}),_strint($rrd->{ds}[$i]->{max});
1443 16         57 $out.=sprintf "%s", "\n\t\t\n\t\t".$rrd->{ds}[$i]->{pdp_prep}->{last_ds}."\n\t\t";
1444 16         41 $out.=sprintf "%s\n\t\t",_strfloat($rrd->{ds}[$i]->{pdp_prep}->{val},$digits);
1445 16         62 $out.=sprintf "%s", "".$rrd->{ds}[$i]->{pdp_prep}->{unkn_sec_cnt}."\n\t";
1446 16         45 $out.=sprintf "%s", "\n";
1447             }
1448 4         8 $out.=sprintf "%s", "\n\t\n";
1449 4         19 for ($i=0; $i<$rrd->{rra_cnt}; $i++) {
1450 20         28 $out.=sprintf "%s", "\t\n\t\t";
1451 20         67 $out.=sprintf "%s", "".$rrd->{rra}[$i]->{name}."\n\t\t";
1452 20         85 $out.=sprintf "%s", "".$rrd->{rra}[$i]->{pdp_cnt}." \n\n\t\t";
1453 20         124 $out.=sprintf "\n\t\t%s\n\t\t\n\t\t",_strfloat($rrd->{rra}[$i]->{xff},$digits);
1454 20         42 $out.=sprintf "%s", "\n\t\t";
1455 20         57 for ($ii=0; $ii<$rrd->{ds_cnt}; $ii++) {
1456 80         188 $out.=sprintf "\t\n\t\t\t%s\n\t\t\t", _strfloat($rrd->{rra}[$i]->{cdp_prep}[$ii]->[PRIMARY_VAL],$digits);
1457 80         237 $out.=sprintf "%s\n\t\t\t", _strfloat($rrd->{rra}[$i]->{cdp_prep}[$ii]->[SECONDARY_VAL],$digits);
1458 80         231 $out.=sprintf "%s\n\t\t\t",_strfloat($rrd->{rra}[$i]->{cdp_prep}[$ii]->[VAL],$digits);
1459 80         266 $out.=sprintf "%s", "". $rrd->{rra}[$i]->{cdp_prep}[$ii]->[UNKN_PDP_CNT]."\n\t\t\t";
1460 80         194 $out.=sprintf "%s", "\n\t\t";
1461             }
1462 20         22 $out.=sprintf "%s", "\n\t\t";
1463 20         24 $out.=sprintf "%s", "\n\t\t";
1464 20         80 $t = $rrd->{last_up} - $rrd->{last_up}%($rrd->{rra}[$i]->{pdp_cnt}*$rrd->{pdp_step}) -($rrd->{rra}[$i]->{row_cnt}-1)*$rrd->{rra}[$i]->{pdp_cnt}*$rrd->{pdp_step};
1465 20         58 for ($j=0; $j<$rrd->{rra}[$i]->{row_cnt}; $j++) {
1466 120         257 $jj= ($rrd->{rra}[$i]->{ptr}+1 + $j)%$rrd->{rra}[$i]->{row_cnt};
1467 120 50       197 if ($timecomments) {$out.=sprintf "\t%s", " ";}
  0         0  
1468 120         112 $out.="";
1469 120         324 @line=$self->_unpackd($rrd->{rra}[$i]->{data}[$jj]);
1470 120         309 for ($ii=0; $ii<$rrd->{ds_cnt}; $ii++) {
1471 480         786 $out.=sprintf "%s",_strfloat($line[$ii],$digits);
1472             }
1473 120         122 $out.=sprintf "%s", "\n\t\t";
1474 120         441 $t+=$rrd->{rra}[$i]->{pdp_cnt}*$rrd->{pdp_step};
1475             }
1476 20         24 $out.=sprintf "%s", "\n\t";
1477 20         52 $out.=sprintf "%s", "\n";
1478             }
1479 4         9 $out.=sprintf "%s", "\n";
1480 4         84 return $out;
1481             }
1482              
1483             ####
1484             sub _saveheader {
1485             # construct binary header for RRD file
1486 3     3   6 my $self=$_[0];
1487 3         4 my $fd=$_[1];
1488              
1489 3         10 my $L=$self->_packlongchar();
1490 3         10 my $header="\0"x $self->_get_header_size; # preallocate header
1491 3         11 substr($header,0,9,"RRD\0".$self->{rrd}->{version});
1492 3         26 substr($header,$self->{OFFSET},$self->{FLOAT_EL_SIZE}+3*$self->{LONG_EL_SIZE}, $self->{COOKIE}.pack("$L $L $L",$self->{rrd}->{ds_cnt}, $self->{rrd}->{rra_cnt}, $self->{rrd}->{pdp_step}));
1493             # DS defs
1494 3         28 my $idx=$self->{STAT_HEADER_SIZE0};
1495 3         30 for (my $i=0; $i<$self->{rrd}->{ds_cnt}; $i++) {
1496 12         67 substr($header,$idx,40+$self->{FLOAT_EL_SIZE},pack("Z20 Z20 $L x".$self->{DIFF_SIZE},
1497             $self->{rrd}->{ds}[$i]->{name}, $self->{rrd}->{ds}[$i]->{type}, $self->{rrd}->{ds}[$i]->{hb}));
1498 12         15 $idx+=40+$self->{FLOAT_EL_SIZE};
1499 12         38 my @minmax=($self->{rrd}->{ds}[$i]->{min}, $self->{rrd}->{ds}[$i]->{max});
1500 12         25 substr($header,$idx,2*$self->{FLOAT_EL_SIZE},$self->_packd(\@minmax));
1501 12         39 $idx+=9*$self->{FLOAT_EL_SIZE};
1502             }
1503             # RRA defs
1504 3         5 my $i;
1505 3         13 for ($i=0; $i<$self->{rrd}->{rra_cnt}; $i++) {
1506 15         70 substr($header,$idx,20+$self->{RRA_DEL_PAD}+2*$self->{LONG_EL_SIZE},pack("Z".(20+$self->{RRA_DEL_PAD})." $L $L",$self->{rrd}->{rra}[$i]->{name}, $self->{rrd}->{rra}[$i]->{row_cnt}, $self->{rrd}->{rra}[$i]->{pdp_cnt}));
1507 15         27 $idx+=20+$self->{RRA_DEL_PAD}+2*$self->{LONG_EL_SIZE};
1508 15         28 my @xff=($self->{rrd}->{rra}[$i]->{xff});
1509 15         30 substr($header,$idx+$self->{RRA_PAD},$self->{FLOAT_EL_SIZE},$self->_packd(\@xff));
1510 15         47 $idx += $self->{FLOAT_EL_SIZE}*10+$self->{RRA_PAD};
1511             }
1512             # live header
1513 3         12 substr($header,$idx,2*$self->{LONG_EL_SIZE},pack("$L $L", $self->{rrd}->{last_up},0));
1514 3         7 $idx+= 2*$self->{LONG_EL_SIZE};
1515             # PDP_PREP
1516 3         15 for ($i=0; $i<$self->{rrd}->{ds_cnt}; $i++) {
1517 12         63 substr($header,$idx,30+$self->{PDP_PREP_PAD}+$self->{FLOAT_EL_SIZE},
1518             pack("Z".(30+$self->{PDP_PREP_PAD})." $L x".$self->{DIFF_SIZE},$self->{rrd}->{ds}[$i]->{pdp_prep}->{last_ds}, $self->{rrd}->{ds}[$i]->{pdp_prep}->{unkn_sec_cnt}));
1519 12         17 $idx+=30+$self->{PDP_PREP_PAD}+$self->{FLOAT_EL_SIZE};
1520 12         26 my @val=($self->{rrd}->{ds}[$i]->{pdp_prep}->{val});
1521 12         22 substr($header,$idx,$self->{FLOAT_EL_SIZE},$self->_packd(\@val));
1522 12         36 $idx+= $self->{FLOAT_EL_SIZE}*9;
1523             }
1524             # CDP_PREP
1525 3         4 my @val; my $ii;
1526 3         12 for (my $ii=0; $ii<$self->{rrd}->{rra_cnt}; $ii++) {
1527 15         34 for ($i=0; $i<$self->{rrd}->{ds_cnt}; $i++) {
1528             # do a bit of code optimisation here
1529 60 50 33     237 if ($self->{encoding} eq "native-double-simple" || $self->{encoding} eq "native-double-mixed") {
    50          
1530 0         0 substr($header,$idx,$self->{CDP_PREP_EL_SIZE}, pack("d $L x".$self->{DIFF_SIZE}." d d d d $L x".$self->{DIFF_SIZE}." $L x".$self->{DIFF_SIZE}." d d",@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}));
  0         0  
1531 0         0 $idx+=$self->{CDP_PREP_EL_SIZE};
1532             } elsif ($self->{encoding} eq "native-single") {
1533 0         0 substr($header,$idx,$self->{CDP_PREP_EL_SIZE}, pack("f $L x".$self->{DIFF_SIZE}." f f f f $L x".$self->{DIFF_SIZE}." $L x".$self->{DIFF_SIZE}." f f",@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}));
  0         0  
1534 0         0 $idx+=$self->{CDP_PREP_EL_SIZE};
1535             } else {
1536 60         67 substr($header,$idx,$self->{FLOAT_EL_SIZE},$self->_packd([@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[0]]));
  60         191  
1537 60         96 $idx+=$self->{FLOAT_EL_SIZE};
1538 60         96 substr($header,$idx,$self->{FLOAT_EL_SIZE},pack("$L x".$self->{DIFF_SIZE},@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[1]));
  60         136  
1539 60         58 $idx+=$self->{FLOAT_EL_SIZE};
1540 60         82 substr($header,$idx,4*$self->{FLOAT_EL_SIZE},$self->_packd([@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[2..5]]));
  60         182  
1541 60         106 $idx+=4*$self->{FLOAT_EL_SIZE};
1542 60         127 substr($header,$idx,2*$self->{FLOAT_EL_SIZE},pack("$L x".$self->{DIFF_SIZE}." $L x".$self->{DIFF_SIZE},@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[6..7]));
  60         150  
1543 60         70 $idx+=2*$self->{FLOAT_EL_SIZE};
1544 60         73 substr($header,$idx,2*$self->{FLOAT_EL_SIZE},$self->_packd([@val=@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[8..9]]));
  60         232  
1545 60         996 $idx+=2*$self->{FLOAT_EL_SIZE};
1546             }
1547             }
1548             }
1549             # RRA PTR
1550 3         25 for ($i=0; $i<$self->{rrd}->{rra_cnt}; $i++) {
1551 15         35 substr($header,$idx,$self->{LONG_EL_SIZE},pack("$L",$self->{rrd}->{rra}[$i]->{ptr}));
1552 15         30 $idx+=$self->{LONG_EL_SIZE};
1553             }
1554             #return $header;
1555 3         73 print $fd $header;
1556             }
1557              
1558             sub save {
1559             # save RRD data to a file
1560 3     3 1 868 my $self=$_[0];
1561            
1562             # load RRA data, if not already loaded
1563 3 100       14 if (!defined($self->{rrd}->{dataloaded})) {$self->_loadRRAdata;}
  1         5  
1564            
1565 3 50       21 if (@_>1) {
    50          
1566             # open file
1567 0         0 $self->{file_name}=$_[1];
1568             } elsif (!defined($self->{file_name})) {
1569 0         0 croak("Must either supply a filename to use or have a file already opened e.g. via calling open()\n");
1570             }
1571 3 50 33 2   122 open $self->{fd}, "+<", $self->{file_name} or open $self->{fd}, ">", $self->{file_name} or croak "Couldn't open file ".$self->{file_name}.": $!\n";
  2         21  
  2         2  
  2         18  
1572 3         2301 binmode($self->{fd});
1573 3         7 my $fd=$self->{fd};
1574              
1575 3 50       10 if (!defined($self->{encoding})) { croak("Current encoding must be defined\n.");}
  0         0  
1576 3         7 my $current_encoding=$self->{encoding};
1577 3 50       11 if (@_>2) {
1578 0         0 my $encoding=$_[2];
1579 0 0       0 if ($encoding !~ m/^(native-double|native-double-simple|native-double-mixed|portable-double|portable-single)$/) {croak("unknown format ".$encoding."\n");}
  0         0  
1580 0 0       0 if ($encoding =~ m/^native-double$/) {$encoding=_native_double();}
  0         0  
1581 0         0 $self->{encoding}=$encoding;
1582             }
1583 3         16 $self->_sizes;
1584              
1585             # output headers
1586             #print $fd $self->getheader();
1587 3         13 $self->_saveheader($fd);
1588              
1589             # output data
1590 3         9 my @line; my $i; my $ii;
  0         0  
1591 3         15 for ($ii=0; $ii<$self->{rrd}->{rra_cnt}; $ii++) {
1592 15         43 for ($i=0; $i<$self->{rrd}->{rra}[$ii]->{row_cnt}; $i++) {
1593 75 50       108 if ($self->{encoding} ne $current_encoding) {
1594             # need to convert binary data encoding
1595 0         0 @line=$self->_unpackd($self->{rrd}->{rra}[$ii]->{data}[$i],$current_encoding);
1596 0         0 $self->{rrd}->{rra}[$ii]->{data}[$i] = $self->_packd(\@line);
1597             }
1598 75         228 print $fd $self->{rrd}->{rra}[$ii]->{data}[$i];
1599             }
1600             }
1601             # done
1602              
1603             # and exit
1604 3         30 return 1;
1605             }
1606              
1607             ####
1608             sub close {
1609             # close an open RRD file
1610 6     6 1 1621 my ($self) = @_;
1611 6 50       26 if (defined($self->{fd})) { close($self->{fd}); }
  6         84  
1612             }
1613              
1614             ####
1615              
1616             sub create {
1617             # create a new RRD
1618 1     1 1 621 my ($self, $args_str) = @_; my $rrd=$self->{rrd};
  1         8  
1619              
1620 1         5 my $last_up=time(); my $pdp_step=300;
  1         1  
1621 1         3 my $encoding="native-double"; # default to RRDTOOL compatible encoding.
1622 1         2 my $ret; my $args;
1623 1         5 ($ret, $args) = _GetOptionsFromString($args_str,
1624             "start|b:i" => \$last_up,
1625             "step|s:i" => \$pdp_step,
1626             "format|f:s" => \$encoding
1627             );
1628 1 50       4 if ($last_up < 3600 * 24 * 365 * 10) { croak("the first entry to the RRD should be after 1980\n"); }
  0         0  
1629 1 50       2 if ($pdp_step <1) {croak("step size should be no less than one second\n");}
  0         0  
1630 1 50       6 if ($encoding !~ m/^(native-double|native-double-simple|native-double-mixed|portable-double|portable-single)$/) {croak("unknown format ".$encoding."\n");}
  0         0  
1631 1 50       3 if ($encoding =~ m/^native-double$/) {$encoding=_native_double();}
  0         0  
1632 1         3 $self->{encoding}=$encoding;
1633 1         5 $self->_sizes;
1634            
1635 1         39 $rrd->{version}="0003";
1636 1         4 $rrd->{ds_cnt}=0; $rrd->{rra_cnt}=0; $rrd->{pdp_step}=$pdp_step;
  1         3  
  1         2  
1637 1         3 $rrd->{last_up}=$last_up;
1638            
1639             # now parse the DS and RRA info
1640 1         3 my $i;
1641 0         0 my $min; my $max;
1642 1         2 for ($i=0; $i<@{$args}; $i++) {
  10         22  
1643 9 100       34 if (${$args}[$i] =~ m/^DS:([a-zA-Z0-9]+):(GAUGE|COUNTER|DERIVE|ABSOLUTE):([0-9]+):(U|[+|-]?[0-9\.]+):(U|[+|-]?[0-9\.]+)$/) {
  9 50       27  
  5         19  
1644 4         2 my $ds;
1645 4 50       6 $min=$4; if ($min eq "U") {$min=NAN;} # set to NaN
  4         11  
  4         3  
1646 4 50       4 $max=$5; if ($max eq "U") {$max=NAN;} # set to NaN
  4         5  
  4         3  
1647 4         24 ($ds->{name}, $ds->{type}, $ds->{hb}, $ds->{min}, $ds->{max},
1648             $ds->{pdp_prep}->{last_ds}, $ds->{pdp_prep}->{unkn_sec_cnt}, $ds->{pdp_prep}->{val}
1649             )= ($1,$2,$3,$min,$max,"U", $last_up%$pdp_step, 0.0);
1650 4         6 $rrd->{ds}[@{$rrd->{ds}}]=$ds;
  4         6  
1651 4         5 $rrd->{ds_cnt}++;
1652             } elsif (${$args}[$i] =~ m/^RRA:(AVERAGE|MAX|MIN|LAST):([0-9\.]+):([0-9]+):([0-9]+)$/) {
1653 5         4 my $rra;
1654 5 50       10 if ($4<1) { croak("Invalid row count $4\n");}
  0         0  
1655 5 50 33     19 if ($2<0.0 || $2>1.0) { croak("Invalid xff $2: must be between 0 and 1\n");}
  0         0  
1656 5 50       11 if ($3<1) { croak("Invalid step $3: must be >= 1\n");}
  0         0  
1657 5         61 ($rra->{name}, $rra->{xff}, $rra->{pdp_cnt}, $rra->{row_cnt}, $rra->{ptr}, $rra->{data})=($1,$2,$3,$4,int(rand($4)),undef);
1658 5         8 $rrd->{rra}[@{$rrd->{rra}}]=$rra;
  5         6  
1659 5         7 $rrd->{rra_cnt}++;
1660             }
1661             }
1662 1 50       2 if ($rrd->{ds_cnt}<1) {croak("You must define at least one Data Source\n");}
  0         0  
1663 1 50       3 if ($rrd->{rra_cnt}<1) {croak("You must define at least one Round Robin Archive\n");}
  0         0  
1664            
1665 1         1 my $ii;
1666 1         4 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
1667 5         7 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
1668 20         27 @{$rrd->{rra}[$ii]->{cdp_prep}[$i]} = (NAN,(($last_up-$last_up%$pdp_step)%($pdp_step*$rrd->{rra}[$ii]->{pdp_cnt}))/$pdp_step,0,0,0,0,0,0,0,0);
  20         65  
1669             }
1670             }
1671            
1672             # initialise the data
1673 1         1 my $j;
1674 1         4 my @empty=((NAN)x$rrd->{ds_cnt});
1675 1         5 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
1676 5         14 for ($i=0; $i<$rrd->{rra}[$ii]->{row_cnt}; $i++) {
1677 25         45 $rrd->{rra}[$ii]->{data}[$i]=$self->_packd(\@empty);
1678             }
1679             }
1680 1         10 $rrd->{dataloaded}=1; # record the fact that the data is now loaded in memory
1681             }
1682              
1683             ####
1684             sub open {
1685             # open an RRD file and read the header; reading of the body of the RRD file (containing the RRA data) is left until actually needed
1686 6     6 1 2235 my $self = $_[0]; my $rrd=$self->{rrd};
  6         30  
1687 6         17 $self->{file_name}=$_[1];
1688            
1689 6 50       308 open($self->{fd}, "<", $self->{file_name}) or croak "Couldn't open file ".$self->{file_name}.": $!\n";
1690 6         25 binmode($self->{fd});
1691 6         77 my $file_len = -s $self->{file_name};
1692              
1693             # check static part of the header (with fixed size)
1694             # header format: {cookie[4], version[5], double float_cookie, ds_cnt, rra_cnt, pdp_step, par[10] (unused array) }
1695 6         110 read($self->{fd},my $staticheader,16+8*NATIVE_DOUBLE_EL_SIZE);
1696 6         41 my $file_cookie = unpack("Z4",substr($staticheader,0,4));
1697 6 50       27 if ($file_cookie ne "RRD") { croak("Wrong magic id $file_cookie\n"); }
  0         0  
1698 6         25 $rrd->{version}=unpack("Z5",substr($staticheader,4,5));
1699 6 50 33     27 if ($rrd->{version} ne "0003" && $rrd->{version} ne "0004") { croak("Unsupported RRD version ".$rrd->{version}."\n");}
  0         0  
1700              
1701             # use float cookie to try to figure out the encoding used, taking account of variable byte alignment (e.g. float cookie starts at byte 12 on 32 bits Intel/Linux machines and at byte 16 on 64 bit Intel/Linux machines)
1702             #my ($x, $y, $byte1, $byte2, $byte3, $byte4, $byte5, $byte6, $byte7, $byte8) =unpack("Z4 Z5 x![L!] C C C C C C C C",substr($staticheader,0,length($staticheader)));
1703             #print $byte1, " ", $byte2, " ",$byte3," ", $byte4," ", $byte5," ", $byte6," ", $byte7," ", $byte8,"\n";
1704 6         13 $self->{encoding}=undef;
1705 6         39 (my $x, my $y, my $t) =unpack("Z4 Z5 x![L!] d",substr($staticheader,0,length($staticheader)));
1706 6         83 my $file_floatcookie_native_double_simple = sprintf("%0.6e", $t);
1707 6         33 ($x, $y, $t) =unpack("Z4 Z5 x![d] d",substr($staticheader,0,length($staticheader)));
1708 6         40 my $file_floatcookie_native_double_mixed = sprintf("%0.6e", $t);
1709 6         38 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_SINGLE_EL_SIZE),"native-single");
1710 6         50 my $file_floatcookie_native_single=sprintf("%0.6e",$t);
1711 6         22 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_SINGLE_EL_SIZE),"portable-single");
1712 6         45 my $file_floatcookie_portable_single=sprintf("%0.6e",$t);
1713 6         27 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_DOUBLE_EL_SIZE),"portable-double");
1714 6         55 my $file_floatcookie_portable_double=sprintf("%0.6e",$t);
1715 6         9 my $file_floatcookie_littleendian_single;
1716             my $file_floatcookie_littleendian_double;
1717 6 50       21 if ($PACK_LITTLE_ENDIAN_SUPPORT>0) {
1718 6         101 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_SINGLE_EL_SIZE),"littleendian-single");
1719 6         43 $file_floatcookie_littleendian_single=sprintf("%0.6e",$t);
1720 6         20 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_DOUBLE_EL_SIZE),"littleendian-double");
1721 6         38 $file_floatcookie_littleendian_double=sprintf("%0.6e",$t);
1722             }
1723 6         12 my $cookie=sprintf("%0.6e",DOUBLE_FLOATCOOKIE);
1724 6         12 my $singlecookie=sprintf("%0.6e",SINGLE_FLOATCOOKIE);
1725 6 50 33     66 if ($file_floatcookie_native_double_simple eq $cookie) {
    50 0        
    100          
    50          
    0          
    0          
    0          
1726 0         0 $self->{encoding} = "native-double-simple";
1727             } elsif ($file_floatcookie_native_double_mixed eq $cookie ) {
1728 0         0 $self->{encoding} = "native-double-mixed";
1729             } elsif ($file_floatcookie_native_single eq $singlecookie ) {
1730 1         3 $self->{encoding} = "native-single";
1731             } elsif ($PACK_LITTLE_ENDIAN_SUPPORT>0 && $file_floatcookie_littleendian_double eq $cookie) {
1732 5         15 $self->{encoding} = "littleendian-double";
1733             } elsif ($PACK_LITTLE_ENDIAN_SUPPORT>0 && $file_floatcookie_littleendian_single eq $singlecookie) {
1734 0         0 $self->{encoding} = "littleendian-single";
1735             } elsif ($file_floatcookie_portable_single eq $singlecookie) {
1736 0         0 $self->{encoding} = "portable-single";
1737             } elsif ($file_floatcookie_portable_double eq $cookie) {
1738 0         0 $self->{encoding} = "portable-double";
1739             } else {
1740 0         0 croak("This RRD was created on incompatible architecture\n");
1741             }
1742             #print $self->{encoding},"\n";
1743             #$self->{encoding} = "portable-double";
1744 6         22 $self->_sizes; # now that we know the encoding, calc the sizes of the various elements in the file
1745 6         20 my $L=$self->_packlongchar;
1746              
1747             # extract info on number of DS's and RRS's, plus the pdp step size
1748 6         60 ($rrd->{ds_cnt}, $rrd->{rra_cnt}, $rrd->{pdp_step}) =unpack("$L $L $L",substr($staticheader,$self->{OFFSET} +$self->{FLOAT_EL_SIZE},3*$self->{LONG_EL_SIZE}));
1749             #print $self->{encoding}," ",$offset," ",$L," ",$self->{FLOAT_EL_SIZE}," ", $self->{LONG_EL_SIZE}," ",$rrd->{ds_cnt}," ",$rrd->{rra_cnt}," ",$rrd->{pdp_step},"\n";
1750              
1751             # read in the full header now;
1752 6         33 seek $self->{fd},0,0; # go back to start of the file
1753 6         38 read($self->{fd},my $header,$self->_get_header_size);
1754             # extract header info into structured arrays
1755 6         16 my $pos=$self->{DS_DEF_IDX};
1756 6         25 $self->_extractDSdefs(\$header,$pos);
1757            
1758 6         16 $pos+=$self->{DS_EL_SIZE}*$rrd->{ds_cnt};
1759 6         24 $self->_extractRRAdefs(\$header,$pos);
1760            
1761 6         12 $pos+=$self->{RRA_DEF_EL_SIZE}*$rrd->{rra_cnt};
1762 6         26 $rrd->{last_up} = unpack("$L",substr($header,$pos,$self->{LONG_EL_SIZE}));
1763            
1764 6         11 $pos+=$self->{LIVE_HEAD_SIZE};
1765 6         35 $self->_extractPDPprep(\$header,$pos);
1766            
1767 6         14 $pos+=$self->{PDP_PREP_EL_SIZE}*$rrd->{ds_cnt};
1768 6         22 $self->_extractCDPprep(\$header,$pos);
1769            
1770 6         22 $pos+=$self->{CDP_PREP_EL_SIZE}*$rrd->{ds_cnt}*$rrd->{rra_cnt};
1771 6         28 $self->_extractRRAptr(\$header,$pos);
1772            
1773 6         14 $pos+=$self->{RRA_PTR_EL_SIZE}*$rrd->{rra_cnt};
1774            
1775             # validate file size
1776 6         8 my $i; my $row_cnt=0;
  6         10  
1777 6         30 for ($i=0; $i<$rrd->{rra_cnt}; $i++) {
1778 30         76 $row_cnt+=$rrd->{rra}[$i]->{row_cnt};
1779             }
1780 6         17 my $correct_len=$self->_get_header_size +$self->{FLOAT_EL_SIZE} * $row_cnt*$rrd->{ds_cnt};
1781 6 50 33     41 if ($file_len < $correct_len || $file_len > $correct_len+8) { # extra 8 bytes here is to allow for padding on Linux/Intel 64 bit platforms
1782 0         0 croak($self->{file_name}." size is incorrect (is $file_len bytes but should be $correct_len bytes)");
1783             }
1784 6         13 $rrd->{dataloaded}=undef; # keep note that data is not loaded yet
1785 6         38 return $self->{encoding};
1786             }
1787              
1788             1;
1789              
1790              
1791             =pod
1792            
1793             =head1 NAME
1794            
1795             RRD::Editor - Portable, standalone (no need for RRDs.pm) tool to create and edit RRD files.
1796            
1797             =head1 SYNOPSIS
1798              
1799             use strict;
1800             use RRD::Editor ();
1801            
1802             # Create a new object
1803             my $rrd = RRD::Editor->new();
1804            
1805             # Create a new RRD with 3 data sources called bytesIn, bytesOut and
1806             # faultsPerSec and one RRA which stores 1 day worth of data at 5 minute
1807             # intervals (288 data points). The argument format is the same as that used
1808             # by 'rrdtool create', see L
1809             $rrd->create("DS:bytesIn:GAUGE:600:U:U DS:bytesOut:GAUGE:600:U:U DS:faultsPerSec:COUNTER:600:U:U RRA:AVERAGE:0.5:1:288")
1810              
1811             # Save RRD to a file
1812             $rrd->save("myfile.rrd");
1813             # The file format to use can also be optionally specified:
1814             # $rrd->save("myfile.rrd","native-double"); # default; non-portable format used by RRDTOOL
1815             # $rrd->save("myfile.rrd","portable-double"); # portable, data stored in double-precision
1816             # $rrd->save("myfile.rrd","portable-single"); # portable, data stored in single-precision
1817              
1818             # Load RRD from a file. Automagically figures out the file format
1819             # (native-double, portable-double etc)
1820             $rrd->open("myfile.rrd");
1821            
1822             # Add new data to the RRD for the same 3 data sources bytesIn,
1823             # bytesOut and faultsPerSec. The argument format is the same as that used by
1824             # 'rrdtool update', see L
1825             $rrd->update("N:10039:389:0.4");
1826            
1827             # Show information about an RRD. Output generated is similar to
1828             # 'rrdtool info'.
1829             print $rrd->info();
1830            
1831             # XML dump of RRD contents. Output generated is similar to 'rrdtool dump'.
1832             print $rrd->dump();
1833            
1834             # Extract data measurements stored in RRAs of type AVERAGE
1835             # The argument format is the same as that used by 'rrdtool fetch' and
1836             # the output generated is also similar, see
1837             # L
1838             print $rrd->fetch("AVERAGE");
1839            
1840             # Get the time when the RRD was last updated (as a unix timestamp)
1841             printf "RRD last updated at %d\n", $rrd->last();
1842              
1843             # Get the measurements added when the RRD was last updated
1844             print $rrd->lastupdate();
1845            
1846             # Get the min step size (or resolution) of the RRD. This defaults to 300s unless specified
1847             otherwise when creating an RRD.
1848             print $rrd->minstep()
1849            
1850             =head2 Edit Data Sources
1851            
1852             # Add a new data-source called bytes. Argument format is the same as $rrd->create().
1853             $rrd->add_DS("DS:bytes:GAUGE:600:U:U");
1854            
1855             # Delete the data-source bytesIn
1856             $rrd->delete_DS("bytesIn");
1857            
1858             # Get a list of the data-sources names
1859             print $rrd->DS_names();
1860            
1861             # Change the name of data-source bytes to be bytes_new
1862             $rrd->rename_DS("bytes", "bytes_new")
1863            
1864             # Get the heartbeat value for data-source bytesOut (the max number of seconds that
1865             # may elapse between data measurements)
1866             printf "Heartbeat for DS bytesOut = %d\n", $rrd->DS_heartbeat("bytesOut");
1867              
1868             # Set the heartbeat value for data-source bytesOut to be 1200 secs
1869             $rrd->set_DS_heartbeat("bytesOut",1200);
1870            
1871             # Get the type (COUNTER, GAUGE etc) of data-source bytesOut
1872             printf "Type of DS bytesOut = %s\n", $rrd->DS_type("bytesOut");
1873            
1874             # Set the type of data-source bytesOut to be COUNTER
1875             $rrd->set_DS_type("bytesOut", "COUNTER");
1876            
1877             # Get the minimum value allowed for measurements from data-source bytesOut
1878             printf "Min value of DS bytesOut = %s\n", $rrd->DS_min("bytesOut");
1879              
1880             # Set the minimum value allowed for measurements from data-source bytesOut to be 0
1881             $rrd->set_DS_min("bytesOut",0);
1882            
1883             # Get the maximum value allowed for measurements from data-source bytesOut
1884             printf "Max value of DS bytesOut = %s\n", $rrd->DS_max("bytesOut");
1885            
1886             # Set the maximum value allowed for measurements from data-source bytesOut to be 100
1887             $rrd->set_DS_max("bytesOut",100);
1888            
1889             =head2 Edit RRAs
1890            
1891             # Add a new RRA which stores 1 weeks worth of data (336 data points) at 30 minute
1892             # intervals (30 mins = 6 x 5 mins)
1893             $rrd->add_RRA("RRA:AVERAGE:0.5:6:336");
1894              
1895             # RRAs are identified by an index in range 0 .. $rrd->num_RRAs(). The index
1896             # of an RRD can also be found using $rrd->info() or $rrd->dump()
1897             my $rra_idx=1;
1898            
1899             # Delete an existing RRA with index $rra_idx.
1900             $rrd->delete_RRA($rra_idx);
1901            
1902             # Get the number of rows/data points stored in the RRA with index $rra_idx
1903             $rra_idx=0;
1904             printf "number of rows of RRA %d = %d\n", $rra_idx, $rrd->RRA_numrows($rra_idx);
1905            
1906             # Change the number of rows/data points stored in the RRA with index
1907             # $rra_idx to be 600.
1908             $rra->resize_RRA($rra_idx, 600);
1909            
1910             # Get the value of bytesIn stored at the 10th row/data-point in the
1911             # RRA with index $rra_idx.
1912             printf "Value of data-source bytesIn at row 10 in RRA %d = %d", $rra_idx, $rra->RRA_el($rra_idx, "bytesIn", 10);
1913            
1914             # Set the value of bytesIn at the 10th row/data-point to be 100
1915             $rra->set_RRA_el($rra_idx, "bytesIn", 10, 100);
1916            
1917             # Get the xff value for the RRA with index $rra_idx
1918             printf "Xff value of RRA %d = %d\n", $rra_idx, $rra->RRA_xff($rra_idx);
1919              
1920             # Set the xff value to 0.75 for the RRA with index $rra_idx
1921             $rra->RRA_xff($rra_idx,0.75);
1922            
1923             # Get the type (AVERAGE, LAST etc) of the RRA with index $rra_idx
1924             print $rrd->RRA_type($rra_idx);
1925            
1926             # Get the step (in seconds) of the RRA with index $rra_idx
1927             print $rrd->RRA_step($rra_idx);
1928              
1929              
1930             =head1 DESCRIPTION
1931              
1932             RRD:Editor implements most of the functionality of RRDTOOL, apart from graphing, plus adds some new editing and portability features. It aims to be portable and self-contained (no need for RRDs.pm).
1933            
1934             RRD::Editor provides the ability to add/delete DSs and RRAs and to get/set most of the parameters in DSs and RRAs (renaming, resizing etc). It also allows the data values stored in each RRA to be inspected and changed individually. That is, it provides almost complete control over the contents of an RRD.
1935            
1936             The RRD files created by RRDTOOL use a binary format (let's call it C) that is not portable across platforms. In addition to this file format, RRD:Editor provides two new portable file formats (C and C) that allow the exchange of files. RRD::Editor can freely convert RRD files between these three formats (C,C and C).
1937            
1938             Notes:
1939              
1940             =over
1941              
1942             =item * times must all be specified as unix timestamps (i.e. -1d, -1w etc don't work, and there is no @ option in rrdupdate).
1943              
1944             =item * there is full support for COUNTER, GAUGE, DERIVE and ABSOLUTE data-source types but the COMPUTE type is only partially supported.
1945              
1946             =item * there is full support for AVERAGE, MIN, MAX, LAST RRA types but the HWPREDICT, MHWPREDICT, SEASONAL etc types are only partially supported).
1947              
1948             =back
1949            
1950             =head1 METHODS
1951            
1952             =head2 new
1953            
1954             my $rrd=new RRD:Editor->new();
1955            
1956             Creates a new RRD::Editor object
1957            
1958             =head2 create
1959            
1960             $rrd->create($args);
1961            
1962             The method will create a new RRD with the data-sources and RRAs specified by C<$args>. C<$args> is a string that contains the same sort of command line arguments that would be passed to C. The format for C<$args> is:
1963            
1964             [--start|-b start time] [--step|-s step] [--format|-f encoding] [DS:ds-name:DST:heartbeat:min:max] [RRA:CF:xff:steps:rows]
1965            
1966             where DST may be one of GAUGE, COUNTER, DERIVE, ABSOLUTE and CF may be one of AVERAGE, MIN, MAX, LAST. Possible values for encoding are C, C, C. If omitted, defaults to C (the non-portable file format used by RRDTOOL). See L for further information.
1967            
1968             =head2 open
1969            
1970             $rrd->open($file_name);
1971            
1972             Load the RRD in the file called C<$file_name>. Only the file header is loaded initially, to improve efficiency, with the body of the file subsequently loaded if needed. The file format (C, C etc) is detected automagically.
1973              
1974             =head2 save
1975            
1976             $rrd->save();
1977             $rrd->save($file_name);
1978             $rrd->save($file_name, $encoding);
1979            
1980             Save RRD to a file called $file_name with format specified by C<$encoding>. Possible values for C<$encoding> are C<"native-double">, C<"portable-double">, C<"portable-single">.
1981            
1982             If omitted, C<$encoding> defaults to the format of the file specified when calling C, or to C<"native-double"> if the RRD has just been created using C. C is the non-portable binary format used by RRDTOOL. C is portable across platforms and stores data as double-precision values. C is portable across platforms and stores data as single-precision values (reducing the RRD file size by approximately half). If interested in the gory details, C is just the C format used by Intel 32-bit platforms (i.e. little-endian byte ordering, 32 bit integers, 64 bit IEEE 754 doubles, storage aligned to 32 bit boundaries) - an arbitrary choice, but not unreasonable since Intel platforms are probably the most widespread at the moment, and it is also compatible with web tools such as javascriptRRD L.
1983            
1984             If the RRD was opened using C, then C<$file_name> is optional and if omitted C will save the RRD to the same file as it was read from.
1985              
1986             =head2 close
1987            
1988             $rrd->close();
1989            
1990             Close an RRD file accessed using C or C. Calling C flushes any cached data to disk.
1991              
1992             =head2 info
1993            
1994             my $info = $rrd->info();
1995            
1996             Returns a string containing information on the DSs and RRAs in the RRD (but not showing the data values stored in the RRAs). Also shows details of the file format (C, C etc) if the RRD was read from a file.
1997            
1998             =head2 dump
1999            
2000             my $dump = $rrd->dump();
2001             my $dump = $rrd->dump($arg);
2002            
2003             Returns a string containing the complete contents of the RRD (including data) in XML format. C<$arg> is optional. Possible values are "--no-header" or "-n", which remove the XML header from the output string.
2004            
2005             =head2 fetch
2006            
2007             my $vals = $rrd->fetch($args);
2008            
2009             Returns a string containing a table of measurement data from the RRD. C<$args> is a string that contains the same sort of command line arguments that would be passed to C. The format for C<$args> is:
2010            
2011             CF [--resolution|-r resolution] [--start|-s start] [--end|-e end]
2012            
2013             where C may be one of AVERAGE, MIN, MAX, LAST. See L for further details.
2014              
2015             =head2 update
2016            
2017             $rrd->update($args);
2018            
2019             Feeds new data values into the RRD. C<$args> is a string that contains the same sort of command line arguments that would be passed to C. The format for C<$args> is:
2020              
2021             [--template:-t ds-name[:ds-name]...] N|timestamp:value[:value...] [timestamp:value[:value...] ...]
2022            
2023             See L for further details.
2024            
2025             Since C is often called repeatedly, in-place updating of RRD files is used where possible for greater efficiency . To understand this, a little knowledge of the RRD file format is needed. RRD files consist of a small header containing details of the DSs and RRAs, and a large body containing the data values stored in the RRAs. Reading the body into memory is relatively costly since it is much larger than the header, and so is only done by RRD::Editor on an "as-needed" basis. So long as the body has not yet been read into memory when C is called, C will update the file on disk i.e. without reading in the body. In this case there is no need to call C. If the body has been loaded into memory when C is called, then the copy of the data stored in memory will be updated and the file on disk left untouched - a call to C is then needed to freshen the file stored on disk. Seems complicated, but its actually ok in practice. If all you want to do is efficiently update a file, just use the following formula:
2026            
2027             $rrd->open($file_name);
2028             $rrd->update($args);
2029             $rrd->close();
2030            
2031             and that's it. If you want to do more, then be sure to call C when you're done.
2032            
2033             =head2 last
2034            
2035             my $unixtime = $rrd->last();
2036            
2037             Returns the time when the data stored in the RRD was last updated. The time is returned as a unix timestamp. This value should not be confused with the last modified time of the RRD file.
2038              
2039             =head2 set_last
2040            
2041             $rrd->set_last($unixtime);
2042            
2043             Set the last update time to equal C<$unixtime>. WARNING: Rarely needed, use with caution !
2044              
2045             =head2 lastupdate
2046            
2047             my @vals=$rrd->lastupdate();
2048            
2049             Return a list containing the data-source values inserted at the most recent update to the RRD
2050            
2051             =head2 minstep
2052            
2053             my $minstep = $rrd->minstep();
2054            
2055             Returns the minimum step size (in seconds) used to store data values in the RRD. RRA data intervals must be integer multiples of this step size. The min step size defaults to 300s when creating an RRD (where it is referred to as the "resolution"). NB: Changing the min step size is hard as it would require resampling all of the stored data, so we leave this "to do".
2056              
2057             =head2 add_DS
2058            
2059             $rrd->add_DS($arg);
2060              
2061             Add a new data-source to the RRD. Only one data-source can be added at a time. Details of the data-source to be added are specified by the string C<$arg>. The format of C<$arg> is:
2062            
2063             [DS:ds-name:DST:heartbeat:min:max]
2064            
2065             where DST may be one of GAUGE, COUNTER, DERIVE, ABSOLUTE i.e. the same format as used for C.
2066              
2067             =head2 delete_DS
2068            
2069             $rrd->delete_DS($ds-name);
2070              
2071             Delete the data-source with name C<$ds-name> from the RRD. WARNING: This will irreversibly delete all of the data stored for the data-source.
2072            
2073             =head2 DS_names
2074            
2075             my @ds-names = $rrd->DS_names();
2076              
2077             Returns a list containing the names of the data-sources in the RRD.
2078              
2079             =head2 rename_DS
2080              
2081             $rrd->rename_DS($ds-name, $ds-newname);
2082            
2083             Change the name of data-source C<$ds-name> to be C<$ds-newname>
2084              
2085             =head2 DS_heartbeat
2086            
2087             my $hb= $rrd->DS_heartbeat($ds-name);
2088            
2089             Returns the current heartbeat (in seconds) of a data-source. The heartbeat is the max number of seconds that may elapse between data measurements before declaring that data is missing.
2090              
2091             =head2 set_DS_heartbeat
2092            
2093             $rrd->set_DS_heartbeat($ds-name,$hb);
2094              
2095             Sets the heartbeat value (in seconds) of data-source C<$ds-name> to be C<$hb>.
2096            
2097             =head2 DS_type
2098            
2099             my $type = $rrd->DS_type($ds-name);
2100            
2101             Returns the type (GAUGE, COUNTER etc) of a data-source.
2102            
2103             =head2 set_DS_type
2104              
2105             $rrd->set_DS_type($ds-name, $type);
2106            
2107             Sets the type of data-source C<$ds-name> to be C<$type>.
2108              
2109             =head2 DS_min
2110              
2111             my $min = $rrd->DS_min($ds-name);
2112            
2113             Returns the minimum allowed for measurements from data-source C<$ds-name>. Measurements below this value are set equal to C<$mi>n when stored in the RRD.
2114            
2115             =head2 set_DS_min
2116            
2117             $rrd->set_DS_min($ds-name, $min);
2118            
2119             Set the minimum value for data-source C<$ds-name> to be C<$min>.
2120            
2121             =head2 DS_max
2122            
2123             my $max = $rrd->DS_max($ds-name);
2124            
2125             Returns the maximum allowed for measurements from data-source C<$ds-name>. Measurements above this value are set equal to C<$max> when stored in the RRD.
2126            
2127             =head2 set_DS_max
2128            
2129             $rrd->set_DS_max($ds-name, $max);
2130            
2131             Set the maximum value for data-source C<$ds-name> to be C<$max>.
2132            
2133             =head2 add_RRA
2134            
2135             $rrd->add_RRA($arg);
2136              
2137             Add a new RRA to the RRD. Only one RRA can be added at a time. Details of the RRA to be added are specified by the string C<$arg>. The format of C<$arg> is:
2138            
2139             [RRA:CF:xff:steps:rows]
2140            
2141             where CF may be one of AVERAGE, MIN, MAX, LAST i.e. the same format as used for C.
2142              
2143             =head2 num_RRAs
2144            
2145             my $num_RRAs = $rrd->num_RRAs();
2146            
2147             Returns the number of RRAs stored in the RRD. Unfortunately, unlike data-sources, RRAs are not named and so are only identified by an index in the range 0 .. C. The index of a specific RRD can be found using C or C.
2148            
2149             =head2 delete_RRA
2150            
2151             $rrd->delete_RRA($rra_idx);
2152            
2153             Delete the RRA with index C<$rra_idx> (see above discussion for how to determine the index of an RRA). WARNING: This will irreversibly delete all of the data stored in the RRA.
2154              
2155             =head2 RRA_numrows
2156              
2157             my $numrows = $rrd->RRA_numrows($rra_idx);
2158            
2159             Returns the number of rows in the RRA with index C<$rra_idx>.
2160              
2161             =head2 resize_RRA
2162              
2163             $rra->resize_RRA($rra_idx, $numrows);
2164            
2165             Change the number of rows to be C<$numrows> in the RRA with index C<$rra_idx>. WARNING: If C<$numrows> is smaller than the current row size, excess data points will be discarded.
2166              
2167             =head2 RRA_el
2168              
2169             my ($t,$val) = $rra->RRA_el($rra_idx, $ds-name, $row);
2170            
2171             Returns the timestamp and the value of data-source C<$ds-name> stored at row C<$row> in the RRA with index C<$rra_idx>. C<$row> must be in the range [0 .. C]. Row 0 corresponds to the oldest data point stored and row C to the most recent data point.
2172              
2173             =head2 set_RRA_el
2174              
2175             $rra->set_RRA_el($rra_idx, $ds-name, $row, $val);
2176            
2177             Set the stored value equal to C<$val> for data-source C<$ds-nam>e stored at row C<$row> in the RRA with index C<$rra_idx>.
2178              
2179             =head2 RRA_xff
2180              
2181             my $xff = $rra->RRA_xff($rra_idx);
2182            
2183             Returns the xff value for the RRA with index C<$rra_idx>. The xff value defines the proportion of an RRA data interval that may contain UNKNOWN data (i.e. missing data) and still be treated as known. For example, an xff value 0.5 in an RRA with data interval 300 seconds (5 minutes) means that if less than 150s of valid data is available since the last measurement, UNKNOWN will be stored in the RRA for the next data point.
2184              
2185             =head2 set_RRA_xff
2186              
2187             $rra->RRA_xff($rra_idx,$xff);
2188            
2189             Sets the xff value to C<$xff> for the RRA with index C<$rra_idx>.
2190            
2191             =head2 RRA_step
2192            
2193             my $step = $rrd->RRA_step($rra_idx);
2194            
2195             Returns the data interval (in seconds) of the RRA with index C<$rra_idx>. NB: Changing the step size is hard as it would require resampling the data stored in the RRA, so we leave this "to do".
2196            
2197             =head2 RRA_type
2198            
2199             my $type = $rrd->RRA_type($rra_idx);
2200            
2201             Returns the type of the RRA with index C<$rra_idx> i.e. AVERAGE, MAX, MIN, LAST etc. NB: Changing the type of an RRA is hard (impossible ?) as the stored data doesn't contain enough information to allow its type to be changed. To change type, its recommended instead to delete the RRA and add a new RRA with the desired type.
2202              
2203             =head1 EXPORTS
2204            
2205             You can export the following functions if you do not want to use the object orientated interface:
2206            
2207             create
2208             open
2209             save
2210             close
2211             update
2212             info
2213             dump
2214             fetch
2215             last
2216             set_last
2217             lastupdate
2218             minstep
2219             add_RRA
2220             delete_RRA
2221             num_RRAs
2222             RRA_numrows
2223             resize_RRA
2224             RRA_type
2225             RRA_step
2226             RRA_xff
2227             set_RRA_xff
2228             add_DS
2229             delete_DS
2230             DS_names
2231             rename_DS
2232             DS_heartbeat
2233             set_DS_heartbeat
2234             DS_min
2235             set_DS_min
2236             DS_max
2237             set_DS_max
2238             DS_type
2239             set_DS_type
2240              
2241             The tag C is available to easily export everything:
2242            
2243             use RRD::Editor qw(:all);
2244            
2245             =head1 Portability/Compatibility with RRDTOOL
2246            
2247             The RRD::Editor code is portable, and so long as you stick to using the C and C file formats the RRD files generated will also be portable. Portability issues arise when the C file format of RRD::Editor is used to store RRDs. This format tries to be compatible with the non-portable binary format used by RRDTOOL, which requires RRD::Editor to figure out nasty low-level details of the platform it is running on (byte ordering, byte alignment, representation used for doubles etc). To date, RRD::Editor and RRDTOOL have been confirmed compatible (i.e. they can read each others RRD files in C format) on the following architectures:
2248              
2249             Intel 386 32bit, Intel 686 32bit, AMD64/Intel x86 64bit, Itanium 64bit, Alpha 64bit, MIPS 32bit, MIPSel 32 bit, MIPS 64bit, PowerPC 32bit, ARMv6 (e.g. Raspberry Pi), SPARC 32bit, SPARC 64bit, SH4
2250            
2251             Known issues:
2252            
2253             On ARMv5 platforms RRD::Editor and RRDTOOL file formats may be only partially compatible (RRD::Editor can read RRDTOOL files, but sometimes not vice-versa depending on ARM config)
2254            
2255             For more information on RRD::Editor portability testing, see L. If your platform is not listed, there is a good chance things will "just work" but double checking that RRDTOOL can read the C format RRD files generated by RRD::Editor, and vice-versa, would be a good idea if that's important to you.
2256            
2257             =head1 SEE ALSO
2258              
2259             L command line interface for RRD::Editor, L, L, L
2260            
2261             =head1 VERSION
2262            
2263             Ver 0.18
2264            
2265             =head1 AUTHOR
2266            
2267             Doug Leith
2268            
2269             =head1 BUGS
2270            
2271             Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
2272            
2273             =head1 COPYRIGHT
2274            
2275             Copyright 2014 D.J.Leith.
2276            
2277             This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
2278            
2279             See http://dev.perl.org/licenses/ for more information.
2280            
2281             =cut
2282              
2283              
2284             __END__