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   94821 use 5.8.8; # nan doesn't seem to be supported properly by perl before this
  4         12  
  4         186  
10 4     4   22 use strict;
  4         6  
  4         150  
11 4     4   20 use warnings;
  4         10  
  4         200  
12              
13             require Exporter;
14 4     4   3958 use POSIX qw/strftime/;
  4         28620  
  4         24  
15 4     4   4476 use Carp qw(croak carp cluck);
  4         8  
  4         239  
16             #use Getopt::Long qw(GetOptionsFromString :config pass_through);
17 4     4   5032 use Getopt::Long qw(:config pass_through);
  4         84938  
  4         28  
18 4     4   10978 use Time::HiRes qw(time);
  4         9906  
  4         20  
19 4     4   900 use Config;
  4         7  
  4         169  
20              
21 4     4   22 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS @ISA);
  4         6  
  4         1024  
22              
23             $VERSION = '0.17';
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   55 use constant NATIVE_LONG_EL_SIZE => $Config{longsize};
  4         7  
  4         10063  
38 4     4   16393 use constant NATIVE_DOUBLE_EL_SIZE => $Config{doublesize};
  4         10  
  4         149  
39 4     4   252 use constant PORTABLE_LONG_EL_SIZE => 4; # 32 bits
  4         8  
  4         160  
40 4     4   20 use constant PORTABLE_SINGLE_EL_SIZE => 4; # IEEE 754 single is 32 bits
  4         7  
  4         145  
41 4     4   27 use constant PORTABLE_DOUBLE_EL_SIZE => 8; # IEEE 754 double is 64 bits
  4         7  
  4         1261  
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   17 my $endiantest=unpack("h*", pack("d","1000.1234"));
47 4 50       16 if ($endiantest eq "c92a329bcf04f804") {
    0          
    0          
48 4         1218 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   22 use constant ENDIAN => _endian();
  4         6  
  4         11  
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   9 eval {
67 4         211 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         11 if (ENDIAN eq "little") {
78 4         610 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   25 use constant DOUBLE_FLOATCOOKIE => 8.642135E130;
  4         9  
  4         214  
89 4     4   21 use constant NATIVE_BINARY_FLOATCOOKIE => _cookie();
  4         8  
  4         9  
90 4     4   36 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         6  
  4         184  
91 4     4   18 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         8  
  4         5149  
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   25329 return $_[0] eq "nan" || $_[0] != $_[0]; # NaN is the only quantity that does not equal itself
116             }
117             sub _isInf {
118 1135   100 1135   4730 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   5 my $nan;
123 4         7 if (ENDIAN eq "little") {
124 4         16 $nan= unpack("d", scalar reverse pack "H*", "7FF8000000000000");# little endian
125 4 50       28 if (_isNan($nan)) { return $nan;}
  4         231  
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   19 my $inf;
142 4         7 if (ENDIAN eq "little") {
143 4         15 $inf= unpack("d", scalar reverse pack "H*", "7FF0000000000000");# little endian
144 4 50       12 if (_isInf($inf)) {return $inf;}
  4         213  
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   1360 if (_isNan($_[0])) {
    100          
    100          
161 232         935 return "nan";
162             } elsif (_isInf($_[0])) {
163 19         53 return "inf" ;
164             } elsif (_isInf(-$_[0])) {
165 19         50 return "-inf" ;
166             } else {
167 535         624 my $digits=10;
168 535 50       971 if ($_[1]) {$digits=$_[1];}
  535         629  
169 535         7863 my $str=sprintf "%0.".$digits."e",$_[0];
170 535 50       2390 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         2932 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   84 if (_isNan($_[0])) {
    50          
    50          
177 38         131 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   33 use constant NAN => _NaN();
  4         8  
  4         11  
188 4     4   24 use constant INF => _Inf();
  4         6  
  4         37  
189              
190             # define index into elements in CDP_PREP array
191 4     4   19 use constant VAL => 0;
  4         7  
  4         163  
192 4     4   18 use constant UNKN_PDP_CNT => 1;
  4         11  
  4         136  
193 4     4   18 use constant HW_INTERCEPT => 2;
  4         34  
  4         277  
194 4     4   105 use constant HW_LAST_INTERCEPT => 3;
  4         6  
  4         249  
195 4     4   145 use constant HW_SLOPE => 4;
  4         137  
  4         221  
196 4     4   18 use constant HW_LAST_SLOPE => 5;
  4         5  
  4         173  
197 4     4   19 use constant NULL_COUNT => 6;
  4         6  
  4         152  
198 4     4   37 use constant LAST_NULL_COUNT=> 7;
  4         6  
  4         170  
199 4     4   26 use constant PRIMARY_VAL => 8;
  4         6  
  4         159  
200 4     4   19 use constant SECONDARY_VAL => 9;
  4         5  
  4         90714  
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   36 my ($string) = shift;
207 17         2905 require Text::ParseWords;
208 17         4520 my @temp=@ARGV;
209 17         66 @ARGV = Text::ParseWords::shellwords($string);
210 17         2012 my $ret = GetOptions(@_);
211 17         5725 my @args=@ARGV;
212 17         36 @ARGV=@temp;
213 17         61 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   56 my $self = $_[0]; my $rrd=$self->{rrd};
  31         49  
220            
221 31         302 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   589 my $encoding=$_[0]->{encoding};
237 352 100       655 if (defined($_[2])) {$encoding=$_[2];}
  2         6  
238              
239 352 50 33     3128 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         163 return pack("d<*", @{$_[1]});
  150         670  
251             }
252 202         202 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         208  
  202         188  
  0         0  
253 202 100 66     746 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         93 for (my $i=0; $i<@{$_[1]}; $i++) {
  234         539  
256 159         154 $f=@{$_[1]}[$i];
  159         224  
257 159 100       248 if (_isNan($f)) {
    100          
    100          
    100          
258 16         20 $sign=0; $exp=255; $significand=1;
  16         19  
  16         16  
259             } elsif ($f == -1 * INF) {
260 4         6 $sign=1; $exp=255; $significand=0;
  4         5  
  4         7  
261             } elsif ($f == INF) {
262 4         5 $sign=0; $exp=255; $significand=0;
  4         6  
  4         4  
263             } elsif ($f == 0) {
264 92         100 $sign=0; $exp=0; $significand=0;
  92         81  
  92         87  
265             } else {
266 43 100       72 $sign = ($f<0) ? 1 : 0;
267 43 100       67 $f = ($f<0) ? -$f : $f;
268             # get the normalized form of f and track the exponent
269 43         42 $shift = 0;
270 43         98 while($f >= 2) { $f /= 2; $shift++; }
  284         254  
  284         412  
271 43   66     122 while($f < 1 && $f>0) { $f *= 2; $shift--; }
  71         82  
  71         218  
272 43         43 $f -= 1;
273             # calculate the binary form (non-float) of the significand data
274 43         53 $significand = int($f*(2**23));
275             # get the biased exponent
276 43         45 $exp = int($shift + ((1<<7) - 1)); # shift + bias
277             }
278 159         401 $string.=pack("V",($sign<<31) | ($exp<<23) | $significand);
279             }
280 75         181 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         153 for (my $i=0; $i<@{$_[1]}; $i++) {
  500         999  
284 373         358 $f=@{$_[1]}[$i];
  373         518  
285 373 100       600 if (_isNan($f)) {
    100          
    100          
    100          
286 136         131 $sign=0; $exp=2047; $significandhi=1;$significandlo=1;
  136         116  
  136         111  
  136         134  
287             } elsif ($f == -1 * INF) {
288 4         6 $sign=1; $exp=2047; $significandhi=0;$significandlo=0;
  4         5  
  4         3  
  4         5  
289             } elsif ($f == INF) {
290 4         3 $sign=0; $exp=2047; $significandhi=0;$significandlo=0;
  4         6  
  4         3  
  4         4  
291             } elsif ($f ==0) {
292 93         100 $sign=0; $exp=0; $significandhi=0;$significandlo=0;
  93         79  
  93         88  
  93         86  
293             } else {
294 136 100       198 $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         116 $shift = 0;
298 136         238 while($f >= 2) { $f /= 2; $shift++; }
  607         512  
  607         904  
299 136   66     374 while($f < 1 && $f>0 ) { $f *= 2; $shift--; }
  210         193  
  210         641  
300 136         126 $f -= 1;
301             # calculate the binary form (non-float) of the significand data
302 136         152 $significandhi = int($f*(2**20));
303 136         168 $significandlo = int( ($f-$significandhi/(2**20))*(2**52));
304             # get the biased exponent
305 136         141 $exp = int($shift + ((1<<10) - 1)); # shift + bias
306             }
307 373         992 $string.=pack("V V",$significandlo, ($sign<<31) | ($exp<<20) | $significandhi);
308             }
309 127         502 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   1030 my $encoding=$_[0]->{encoding};
322 587 100       1546 if (defined($_[2])) {$encoding=$_[2];}
  30         55  
323            
324 587 50 33     6181 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         374 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         31 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         1927 return unpack("d<*", $_[1]);
336             }
337 37         45 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     207 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         47 for ($i=0; $i
341 6         48 $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         19 $expo = (($word & 0x7F800000) >> 23) - 127;
343 6         14 $mant = (($word & 0x007FFFFF) | 0x00800000);
344 6 100       20 $sign = ($word & 0x80000000) ? -1 : 1;
345 6 50 33     87 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         65 $num = $sign * (2**($expo-23))*$mant;
353             }
354 6         26 push (@list, $num);
355             }
356 6         23 return @list;
357             } elsif ($encoding eq "portable-double" || $encoding eq "ieee-64") {
358             # manually unpack IEEE 754 64 bit double-precision number.
359 31         94 for ($i=0; $i
360 106         336 $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         299 $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         132 $expo = (($word & 0x7FF00000) >> 20) - 1023;
363 106         116 $manthi = ($word & 0x000FFFFF) ;
364 106 100       161 $sign = ($word & 0x80000000) ? -1 : 1;
365 106 50 66     493 if ($expo == 1024 && $mantlo == 0 && $manthi==0 ) {
    100 33        
    100 100        
      66        
366 0         0 $num=$sign * INF;
367             } elsif ($expo == 1024) {
368 16         24 $num=NAN;
369             } elsif ($expo==-1023 && $manthi==0 && $mantlo==0) {
370 1         2 $num=0;
371             } else {
372 89         277 $num = $sign * ( (2**$expo) + (2**($expo-20))*$manthi + (2**($expo-52))*$mantlo );
373             }
374 106         252 push (@list, $num);
375             }
376 31         124 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   58 my $self=$_[0];
386 39 50 33     203 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         94 return "V";
392             }
393             }
394              
395             ####
396             sub _sizes {
397             # define the sizes of the various elements in RRD binary file
398 10     10   25 my ($self)=@_;
399            
400 10         25 $self->{OFFSET} = 12; # byte position of start of float cookie.
401 10         24 $self->{RRA_DEL_PAD} = 0; # for byte alignment in RRA_DEF after char(20) string
402 10         23 $self->{STAT_PAD} = 0; # for byte alignment at end of static header.
403 10         24 $self->{RRA_PAD} = 0; # for byte alignment at end of RRAD_DEF float array
404 10 50 33     339 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         7 $self->{LONG_EL_SIZE} = PORTABLE_LONG_EL_SIZE;
423 2         12 $self->{FLOAT_EL_SIZE}= PORTABLE_SINGLE_EL_SIZE; # 32 bits
424 2         9 my @cookie=(SINGLE_FLOATCOOKIE);
425 2         9 $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         17 $self->{FLOAT_EL_SIZE}= PORTABLE_DOUBLE_EL_SIZE; # 64 bits
429 8         21 $self->{COOKIE} = PORTABLE_BINARY_FLOATCOOKIE;
430             }
431 10         166 $self->{DIFF_SIZE} = $self->{FLOAT_EL_SIZE} - $self->{LONG_EL_SIZE};
432 10         35 $self->{STAT_HEADER_SIZE} = $self->{OFFSET} + $self->{FLOAT_EL_SIZE} + 3 * $self->{LONG_EL_SIZE};
433 10         34 $self->{STAT_HEADER_SIZE0} = $self->{STAT_HEADER_SIZE} + 10 * $self->{FLOAT_EL_SIZE} + $self->{STAT_PAD};
434 10         19 $self->{RRA_PTR_EL_SIZE} = $self->{LONG_EL_SIZE};
435 10         28 $self->{CDP_PREP_EL_SIZE} = 10 * $self->{FLOAT_EL_SIZE};
436 10         21 $self->{PDP_PREP_PAD} = 2; # for byte alignment of char(30) string in PDP_PREP
437 10         40 $self->{PDP_PREP_EL_SIZE} = 30 + $self->{PDP_PREP_PAD} + 10 * $self->{FLOAT_EL_SIZE};
438 10         53 $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         25 $self->{DS_DEF_IDX} = $self->{STAT_HEADER_SIZE0};
440 10         24 $self->{DS_EL_SIZE} = 40 + 10 * $self->{FLOAT_EL_SIZE} ;
441 10         23 $self->{LIVE_HEAD_SIZE} = 2 * $self->{LONG_EL_SIZE};
442 10         24 $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   13 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         12  
449            
450 6         9 my $i;
451 6         19 my $L=$self->_packlongchar();
452 6         18 @{$rrd->{ds}}=[];
  6         77  
453 6         29 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
454 24         40 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         39 ($ds->{name}, $ds->{type}, $ds->{hb})= unpack("Z20 Z20 $L",substr(${$header},$idx,40+$self->{LONG_EL_SIZE}));
  24         189  
457 24         43 ($ds->{min}, $ds->{max})= $self->_unpackd(substr(${$header},$idx+40+$self->{FLOAT_EL_SIZE},2*$self->{FLOAT_EL_SIZE}));
  24         89  
458 24         62 $rrd->{ds}[$i] = $ds;
459 24         84 $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   13 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         27  
468              
469 6         10 my $i;
470 6         14 my $L=$self->_packlongchar();
471 6         15 @{$rrd->{rra}}=[];
  6         152  
472 6         31 for ($i=0; $i<$rrd->{rra_cnt}; $i++) {
473 30         62 my $rra={};
474 30         80 ($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         167  
475 30         58 ($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         304  
476 30         102 $rrd->{rra}[$i] = $rra;
477 30         109 $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   13 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         14  
486              
487 6         10 my $i;
488 6         16 my $L=$self->_packlongchar();
489 6         16 @{$rrd->{pdp_prep}}=[];
  6         22  
490 6         25 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
491 24         35 my $pdp={};
492 24         59 ($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         129  
493 24         43 ($pdp->{val})= $self->_unpackd(substr(${$header},$idx+30+$self->{PDP_PREP_PAD}+$self->{FLOAT_EL_SIZE},$self->{FLOAT_EL_SIZE}));
  24         89  
494 24         68 $rrd->{ds}[$i]->{pdp_prep} = $pdp;
495 24         77 $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   13 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         13  
504            
505 6         11 my $i; my $ii;
506 6         16 my $L=$self->_packlongchar();
507 6         35 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
508             #@{$rrd->{cdp_prep}[$ii]}=[];
509 30         83 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     573 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         57 @{$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         80  
  20         56  
516 20         69 $idx+=$self->{CDP_PREP_EL_SIZE};
517             } else {
518 100         131 @{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}=(0,0,0,0,0,0,0,0,0,0); # pre-allocate array
  100         432  
519 100         121 (@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[0])=$self->_unpackd(substr(${$header},$idx,$self->{FLOAT_EL_SIZE})); $idx+=$self->{FLOAT_EL_SIZE};
  100         286  
  100         288  
  100         205  
520 100         166 @{$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         308  
  100         224  
  100         145  
521 100         119 @{$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         806  
  100         275  
  100         231  
522 100         221 @{$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         371  
  100         454  
  100         184  
523 100         120 @{$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         338  
  100         281  
  100         587  
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   14 my ($self, $header, $idx) = @_; my $rrd=$self->{rrd};
  6         15  
533              
534 6         15 my $L=$self->_packlongchar();
535 6         13 my @ptr=unpack("$L*",substr(${$header},$idx,$self->{RRA_PTR_EL_SIZE}*$rrd->{rra_cnt}));
  6         28  
536 6         14 my $i;
537 6         21 for ($i=0; $i<$rrd->{rra_cnt}; $i++) {
538 30         94 $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   10 my $self = $_[0]; my $rrd=$self->{rrd};
  4         9  
548 4 50       16 if (!defined($self->{fd})) {croak("loadRRDdata: must call open() first\n");}
  0         0  
549              
550 4         6 my $data; my $ds_cnt=$self->{FLOAT_EL_SIZE} * $rrd->{ds_cnt};
  4         14  
551 4         18 seek $self->{fd},$self->_get_header_size,0; # move to start of RRA data within file
552 4         23 for (my $ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
553 20         38 my $idx=0;
554 20         114 read($self->{fd}, $data, $self->{FLOAT_EL_SIZE} * $rrd->{ds_cnt}* $rrd->{rra}[$ii]->{row_cnt} );
555 20         36 my $row_cnt=$rrd->{rra}[$ii]->{row_cnt};
556 20         49 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         294 $rrd->{rra}[$ii]->{data}[$i]=substr($data,$idx,$ds_cnt);
560 100         233 $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   41 my ($self, $name) = @_; my $rrd=$self->{rrd};
  23         36  
571 23         29 my $i;
572 23         64 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
573 39 100       127 if ($rrd->{ds}[$i]->{name} eq $name) {
574 23         57 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 1458 my $self;
584 3         12 $self->{file_name}=undef; # name of RRD file
585 3         10 $self->{fd}=undef; # file handle
586 3         9 $self->{encoding}=undef; # binary encoding within file.
587 3         9 $self->{rrd}->{version}=undef;
588 3         9 $self->{rrd}->{rra_cnt}= undef; # number of RRAs
589 3         9 $self->{rrd}->{ds_cnt}=undef; # number of DSs
590 3         8 $self->{rrd}->{pdp_step}=undef; # min time step size
591 3         6 $self->{rrd}->{last_up} = undef; # time when last updated
592 3         7 $self->{rrd}->{ds}=undef; # array of DS definitions
593 3         9 $self->{rrd}->{rra}=undef; # array of RRA info
594 3         11 $self->{rrd}->{dataloaded}=undef; # has body of RRD file been loaded into memory ?
595 3         6 bless $self;
596 3         10 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         4 my @names=(); my $i;
  2         3  
603 2         9 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
604 8         32 push(@names, $rrd->{ds}[$i]->{name});
605             }
606 2         15 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 5 my ($self, $rraidx) = @_; my $rrd=$self->{rrd};
  2         4  
617 2 50 33     16 if ($rraidx > $rrd->{rra_cnt} || $rraidx<0) {croak("RRA index out of range\n");}
  0         0  
618 2         13 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 5 my ($self, $rraidx) = @_; my $rrd=$self->{rrd};
  2         5  
631 2 50 33     14 if ($rraidx > $rrd->{rra_cnt} || $rraidx<0) {croak("RRA index out of range\n");}
  0         0  
632 2         15 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         5  
638 2 50 33     14 if ($idx > $rrd->{rra_cnt} || $idx<0) {croak("RRA index out of range\n");}
  0         0  
639 2         20 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 11 my ($self, $rraidx, $ds_name, $tidx) = @_; my $rrd=$self->{rrd};
  3         7  
647            
648 3 50 33     20 if ($rraidx > $rrd->{rra_cnt} || $rraidx<0) {croak("RRA index out of range\n");}
  0         0  
649 3         10 my $dsidx=$self->_findDSidx($ds_name);
650 3 50 33     20 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       10 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  0         0  
654              
655 3         15 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         8 my $jj= ($rrd->{rra}[$rraidx]->{ptr}+1+ $tidx)%$rrd->{rra}[$rraidx]->{row_cnt};
657 3         11 my @line=$self->_unpackd($rrd->{rra}[$rraidx]->{data}[$jj]);
658 3         31 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 5 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         5  
670              
671 1         7 my $jj= ($rrd->{rra}[$rraidx]->{ptr}+1 + $tidx)%$rrd->{rra}[$rraidx]->{row_cnt};
672 1         21 my @line=$self->_unpackd($rrd->{rra}[$rraidx]->{data}[$jj]);
673 1         4 $line[$dsidx] = $val;
674 1         5 $rrd->{rra}[$rraidx]->{data}[$jj]=$self->_packd(\@line);
675             }
676              
677             sub last {
678             # return time of last update
679 3     3 1 22 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 9 my $self=$_[0]; my $rrd=$self->{rrd};
  3         6  
691              
692 3         7 my @vals;
693 3         12 for (my $i=0; $i<$rrd->{ds_cnt}; $i++) {
694 12         46 push(@vals,$rrd->{ds}[$i]->{pdp_prep}->{last_ds});
695             }
696 3         22 return @vals;
697             }
698              
699             sub minstep {
700             # return the min step size, in seconds
701 2     2 1 5 my $self=$_[0]; my $rrd=$self->{rrd};
  2         4  
702 2         10 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       7 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  2         8  
  0         0  
710 2         22 return $rrd->{ds}[$idx]->{hb};
711             }
712              
713             sub set_DS_heartbeat {
714             # change heartbeat for DS
715 1     1 1 2 my ($self, $name, $hb) = @_; my $rrd=$self->{rrd};
  1         3  
716            
717 1 50       4 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       9 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         3  
  0         0  
720             # update to new value
721 1         3 $rrd->{ds}[$idx]->{hb}=$hb;
722 1         4 return 1;
723             }
724              
725             sub DS_min {
726             # return min value for DS
727 1     1 1 3 my ($self, $name) = @_; my $rrd=$self->{rrd};
  1         3  
728            
729 1 50       4 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         4  
  0         0  
730 1         5 return $rrd->{ds}[$idx]->{min};
731             }
732              
733             sub set_DS_min {
734             # change min value for DS
735 1     1 1 16 my ($self, $name, $min) = @_; my $rrd=$self->{rrd};
  1         3  
736            
737 1 50       5 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         22  
  0         0  
738             # update to new value
739 1         3 $rrd->{ds}[$idx]->{min}=$min;
740 1         5 return 1;
741             }
742              
743             sub DS_max {
744             # return max value for DS
745 1     1 1 5 my ($self, $name) = @_; my $rrd=$self->{rrd};
  1         3  
746            
747 1 50       3 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         5  
  0         0  
748 1         6 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       3 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         5  
  0         0  
756             # update to new value
757 1         4 $rrd->{ds}[$idx]->{max}=$max;
758 1         4 return 1;
759             }
760              
761             sub DS_type {
762             # return type of DS
763 2     2 1 5 my ($self, $name) = @_; my $rrd=$self->{rrd};
  2         4  
764            
765 2 50       6 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  2         8  
  0         0  
766 2         10 return $rrd->{ds}[$idx]->{type};
767             }
768              
769             sub set_DS_type {
770             # change type of DS
771 1     1 1 3 my ($self, $name, $type) = @_; my $rrd=$self->{rrd};
  1         2  
772            
773 1 50       4 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         4  
  0         0  
774 1 50       12 if ($type !~ m/(GAUGE|COUNTER|DERIVE|ABSOLUTE)/) { croak("Invalid DS type\n");}
  0         0  
775             # update to new value
776 1         3 $rrd->{ds}[$idx]->{type}=$type;
777 1         5 return 1;
778             }
779              
780             sub rename_DS {
781 1     1 1 3 my ($self, $old, $new) = @_; my $rrd=$self->{rrd};
  1         2  
782              
783 1 50       4 my $idx=$self->_findDSidx($old); if ($idx<0) {croak("Unknown source\n");}
  1         3  
  0         0  
784 1         2 $rrd->{ds}[$idx]->{name}=$new;
785 1         4 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         2  
791            
792 1 50       10 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       34 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  1         4  
796              
797             # update DS definitions
798 1         3 my $ds;
799 1 50       3 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         3  
  1         2  
801 1         15 ($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         3 $rrd->{ds}[@{$rrd->{ds}}]=$ds;
  1         2  
805 1         3 $rrd->{ds_cnt}++;
806            
807             # update RRAs
808 1         2 my $ii;
809 1         4 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
810 5         16 @{$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         36  
811             }
812             # update data
813 1         2 my @line; my $i;
814 1         5 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
815 5         23 for ($i=0; $i<$rrd->{rra}[$ii]->{row_cnt}; $i++) {
816 25         68 @line=$self->_unpackd($rrd->{rra}[$ii]->{data}[$i]);
817 25         51 $line[$rrd->{ds_cnt}-1]=NAN;
818 25         47 $rrd->{rra}[$ii]->{data}[$i]=$self->_packd(\@line);
819             }
820             }
821 1         6 return 1;
822             }
823              
824             sub delete_DS {
825             # delete a DS
826 1     1 1 3 my ($self, $name) = @_; my $rrd=$self->{rrd};
  1         3  
827 1 50       11 my $idx=$self->_findDSidx($name); if ($idx<0) {croak("Unknown source\n");}
  1         4  
  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         3 my $i;
834 1         2 $rrd->{ds_cnt}--;
835 1         4 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         1 my $ii;
841 1         6 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
842 5         11 for ($i=$idx; $i<$rrd->{ds_cnt}; $i++) {
843 10         48 $rrd->{rra}[$ii]->{cdp_prep}[$i]=$rrd->{rra}[$ii]->{cdp_prep}[$i+1];
844             }
845             }
846              
847             # update data
848 1         3 my $j; my @line;
849 1         5 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
850 5         16 for ($i=0; $i<$rrd->{rra}[$ii]->{row_cnt}; $i++) {
851 25         53 @line=$self->_unpackd($rrd->{rra}[$ii]->{data}[$i]);
852 25         57 for ($j=$idx; $j<$rrd->{ds_cnt}; $j++) {
853 50         103 $line[$j]=$line[$j+1];
854             }
855 25         73 $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 4 my ($self, $args) = @_; my $rrd=$self->{rrd};
  1         223  
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       4 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     13 if ($2<0.0 || $2>1.0) { croak("Invalid xff $2: must be between 0 and 1\n");}
  0         0  
871 1 50       5 if ($3<1) { croak("Invalid step $3: must be >= 1\n");}
  0         0  
872 1         69 ($rra->{name}, $rra->{xff}, $rra->{pdp_cnt}, $rra->{row_cnt}, $rra->{ptr}, $rra->{data})=($1,$2,$3,$4,int(rand($4)),undef);
873 1         3 my $idx=@{$rrd->{rra}};
  1         3  
874 1         4 $rrd->{rra}[$idx]=$rra;
875 1         2 $rrd->{rra_cnt}++;
876            
877 1         3 my $i;
878 1         6 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
879 4         19 @{$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         6 for ($i=0; $i<$rrd->{rra}[$idx]->{row_cnt}; $i++) {
884 10         24 $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 3 my ($self, $idx) = @_; my $rrd=$self->{rrd};
  1         3  
892 1 50 33     11 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       6 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  0         0  
895             # update RRA
896 1         3 $rrd->{rra_cnt}--;
897 1         6 for (my $i=$idx; $i<$rrd->{rra_cnt}; $i++) {
898 5         26 $rrd->{rra}[$i]=$rrd->{rra}[$i+1];
899             }
900 1         5 return 1;
901             }
902              
903             sub resize_RRA {
904 1     1 1 4 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       6 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         23 for (my $i=$rrd->{rra}[$idx]->{row_cnt}; $i<$size; $i++) {
913 15         34 $rrd->{rra}[$idx]->{data}[$i] = $self->_packd(\@empty);
914             }
915 1         4 $rrd->{rra}[$idx]->{row_cnt} = $size;
916 1         7 return 1;
917             }
918              
919             sub set_RRA_xff {
920             # schange xff value for an RRA
921 1     1 1 10 my ($self, $idx, $xff) = @_; my $rrd=$self->{rrd};
  1         3  
922 1 50 33     9 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         8 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 28 my ($self, $args_str) = @_; my $rrd=$self->{rrd};
  10         19  
935            
936 10         11 my $ret; my $args; my $template='';
  10         16  
937 10         26 ($ret, $args) = _GetOptionsFromString($args_str,
938             "template|t:s" => \$template,
939             );
940              
941             # update file in place ?
942 10         19 my $inplace; my $fd;
943 10 50       59 if (defined($rrd->{dataloaded})) {
944 10         21 $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         25 my @tmp=split(/:/,$template);
959 10         11 my @idx;
960 10 100       23 if (@tmp == 0) {
961             # no template, default to complete DS list
962 8         26 @idx=(0 .. $rrd->{ds_cnt}-1);
963             } else {
964             # read DS list from template
965 2         11 for ($i=0; $i<@tmp; $i++) {
966 7 50       57 $idx[$i]=$self->_findDSidx($tmp[$i]); if($idx[$i]<0) {croak("Unknown DS name ".$tmp[$i]."\n");}
  7         28  
  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         15 my @updvals; my @bits; my $rate; my $current_time; my $interval;
  0         0  
  0         0  
  0         0  
972 10         18 for ($i=0; $i<@{$args}; $i++) {
  20         51  
973             #parse colon-separated DS string
974 10 50       31 if ($args->[$i] =~ m/(-t|--template)/) {next;} # ignore option here
  0         0  
975 10 50       58 if ($args->[$i] =~ m/\@/) {croak("\@ time format not supported - use either N or a unix timestamp\n");}
  0         0  
976 10         42 @bits=split(/:/,$args->[$i]);
977 10 50       33 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       21 if ($bits[0] eq "N") {
980 0         0 $current_time=time();
981             #normalize_time
982             } else {
983 10         18 $current_time=$bits[0];
984             }
985 10 50       33 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         18 $interval=$current_time - $rrd->{last_up};
987             # initialise values to NaN
988 10         29 for ($j=0; $j<$rrd->{ds_cnt}; $j++) {
989 40         142 $updvals[$j]="U";
990             }
991 10         26 for ($j=0; $j<@idx; $j++) {
992 39         96 $updvals[$idx[$j]] = $bits[$j+1];
993             }
994             # process the data sources and update the pdp_prep area accordingly
995 10         16 my @pdp_new=();
996 10         27 for ($j=0;$j<@updvals; $j++) {
997 40 50       111 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     191 if ($updvals[$j] ne "U" && $rrd->{ds}[$j]->{hb} >= $interval) {
1002 39         44 $rate=NAN;
1003 39 100       131 if ( $rrd->{ds}[$j]->{type} eq "COUNTER" ) {
    100          
    100          
1004 10 50       42 if ($updvals[$j] !~ m/^\d+$/) {croak("not a simple unsigned integer ".$updvals[$j]);}
  0         0  
1005 10 100       30 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       29 if ($pdp_new[$j] < 0) {$pdp_new[$j]+=4294967296; } #2^32
  3         4  
1010 9 50       21 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       33 if ($updvals[$j] !~ m/^[+|-]?\d+$/) {croak("not a simple signed integer ".$updvals[$j]);}
  0         0  
1017 9 100       33 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         24 $pdp_new[$j] = $updvals[$j] - $rrd->{ds}[$j]->{pdp_prep}->{last_ds};
1020 8         13 $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       49 if ($updvals[$j] !~ m/^(-)?[\d]+(\.[\d]+)?$/) {croak("not a number ".$updvals[$j]);}
  0         0  
1026 10         30 $pdp_new[$j] = $updvals[$j]*$interval;
1027 10         17 $rate=$pdp_new[$j]/$interval;
1028             } else { # ABSOLUTE
1029 10         19 $pdp_new[$j] = $updvals[$j];
1030 10         21 $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         2 $pdp_new[$j]=NAN;
1041             }
1042 40         160 $rrd->{ds}[$j]->{pdp_prep}->{last_ds} = $updvals[$j];
1043             }
1044             # how many PDP steps have elapsed since the last update?
1045 10         26 my $proc_pdp_st = $rrd->{last_up} - $rrd->{last_up} % $rrd->{pdp_step};
1046 10         15 my $occu_pdp_age = $current_time % $rrd->{pdp_step};
1047 10         12 my $occu_pdp_st = $current_time - $occu_pdp_age;
1048 10         11 my $pre_int; my $post_int;
1049 10 50       24 if ($occu_pdp_st > $proc_pdp_st) {
1050             # OK we passed the pdp_st moment
1051 10         14 $pre_int = $occu_pdp_st - $rrd->{last_up};
1052 10         14 $post_int = $occu_pdp_age;
1053             } else {
1054 0         0 $pre_int = $interval;
1055 0         0 $post_int=0;
1056             }
1057 10         27 my $proc_pdp_cnt = int( $proc_pdp_st / $rrd->{pdp_step} );
1058 10         18 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       20 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         11 my $pre_unknown; my @pdp_temp; my $diff_pdp_st;
  0         0  
1075 10         30 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         45 $pre_unknown=0;
1079 40 100       71 if (_isNan($pdp_new[$j])) {
1080 3         14 $pre_unknown=$pre_int;
1081             } else {
1082             #print $rrd->{ds}[$j]->{pdp_prep}->{val}," ";
1083 37 100       80 if (_isNan($rrd->{ds}[$j]->{pdp_prep}->{val})) {
1084 2         4 $rrd->{ds}[$j]->{pdp_prep}->{val} = 0;
1085             }
1086 37         94 $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     212 if ($interval > $rrd->{ds}[$j]->{hb} || $rrd->{pdp_step}/2.0 < $rrd->{ds}[$j]->{pdp_prep}->{unkn_sec_cnt}+$pre_unknown) {
1090 5         2539 $pdp_temp[$j]=NAN;
1091             } else {
1092 35         103 $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       72 if (_isNan($pdp_new[$j])) {
1096 3         12 $rrd->{ds}[$j]->{pdp_prep}->{unkn_sec_cnt} = int($post_int);
1097 3         16 $rrd->{ds}[$j]->{pdp_prep}->{val}=NAN;
1098             } else {
1099 37         63 $rrd->{ds}[$j]->{pdp_prep}->{unkn_sec_cnt} = 0;
1100 37         145 $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         12 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         32 my $idx=$self->_get_header_size; # file position (used by in place updates)
1108 10         35 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
1109 50         112 $start_pdp_offset = $rrd->{rra}[$ii]->{pdp_cnt} - $proc_pdp_cnt % $rrd->{rra}[$ii]->{pdp_cnt};
1110 50 100       82 if ($start_pdp_offset <= $elapsed_pdp_st) {
1111 30         75 $rra_step_cnt[$ii] = int(($elapsed_pdp_st - $start_pdp_offset)/$rrd->{rra}[$ii]->{pdp_cnt}) + 1;
1112             } else {
1113 20         29 $rra_step_cnt[$ii] = 0;
1114             }
1115             # update_cdp_prep. update CDP_PREP areas, loop over data sources within each RRA
1116 50         103 for ($j=0; $j<$rrd->{ds_cnt}; $j++) {
1117 200 100       393 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       234 if ($rra_step_cnt[$ii]>0) {
1120 80 100       118 if (_isNan($pdp_temp[$j])) {
1121 16         39 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] +=$start_pdp_offset;
1122 16         36 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[SECONDARY_VAL] = NAN;
1123             } else {
1124 64         138 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[SECONDARY_VAL] = $pdp_temp[$j];
1125             }
1126 80 100       285 if ($rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] > $rrd->{rra}[$ii]->{pdp_cnt}*$rrd->{rra}[$ii]->{xff}) {
1127 16         34 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = NAN;
1128             } else {
1129             #initialize_cdp_val
1130 64 100       189 if ($rrd->{rra}[$ii]->{name} eq "AVERAGE") {
    100          
    100          
1131 16 50       32 if (_isNan($rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL])) {
1132 0         0 $cum_val=0.0;
1133             } else {
1134 16         32 $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         20 $cur_val = $pdp_temp[$j];
1140             }
1141 16         59 $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       33 if (_isNan($rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL])) {
1144 0         0 $cum_val=-1 * INF;
1145             } else {
1146 16         29 $cum_val = $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL];
1147             }
1148 16 50       32 if (_isNan($pdp_temp[$j])) {
1149 0         0 $cur_val=-1 * INF;
1150             } else {
1151 16         24 $cur_val = $pdp_temp[$j];
1152             }
1153 16 100       23 if ($cur_val > $cum_val) {
1154 8         19 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $cur_val;
1155             } else {
1156 8         19 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $cum_val;
1157             }
1158             } elsif ($rrd->{rra}[$ii]->{name} eq "MIN") {
1159 16 50       44 if (_isNan($rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL])) {
1160 0         0 $cum_val=INF;
1161             } else {
1162 16         34 $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         17 $cur_val = $pdp_temp[$j];
1168             }
1169 16 100       29 if ($cur_val < $cum_val) {
1170 8         20 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $cur_val;
1171             } else {
1172 8         26 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $cum_val;
1173             }
1174             } else {
1175 16         33 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $pdp_temp[$j];
1176             }
1177             }
1178             #*cdp_val = initialize_carry_over
1179 80         123 $pdp_into_cdp_cnt=($elapsed_pdp_st - $start_pdp_offset) % $rrd->{rra}[$ii]->{pdp_cnt};
1180 80 50 33     174 if ($pdp_into_cdp_cnt == 0 || _isNan($pdp_temp[$j])) {
1181 80 100       253 if ($rrd->{rra}[$ii]->{name} eq "MAX") {
    100          
    100          
1182 20         37 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=-1 * INF;
1183             } elsif ($rrd->{rra}[$ii]->{name} eq "MIN") {
1184 20         48 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=INF;
1185             } elsif ($rrd->{rra}[$ii]->{name} eq "AVERAGE") {
1186 20         37 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=0;
1187             } else {
1188 20         42 $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       117 if (_isNan($pdp_temp[$j])) {
1198 16         73 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] = ($elapsed_pdp_st - $start_pdp_offset) % $rrd->{rra}[$ii]->{pdp_cnt};
1199             } else {
1200 64         255 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] = 0;
1201             }
1202             } else {
1203 80 100       132 if (_isNan($pdp_temp[$j])) {
1204 4         17 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[UNKN_PDP_CNT] += $elapsed_pdp_st;
1205             } else {
1206             #*cdp_val =calculate_cdp_val
1207 76 100       216 if (_isNan($rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL])) {
1208 19 50       50 if ($rrd->{rra}[$ii]->{name} eq "AVERAGE") {
1209 0         0 $pdp_temp[$j] *= $elapsed_pdp_st;
1210             }
1211 19         66 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]=$pdp_temp[$j];
1212             } else {
1213 57 100       201 if ($rrd->{rra}[$ii]->{name} eq "AVERAGE") {
    100          
    50          
1214 19         76 $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         68 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL] = $pdp_temp[$j];
1218             }
1219             } elsif ($rrd->{rra}[$ii]->{name} eq "MAX") {
1220 19 50       54 if ($pdp_temp[$j] > $rrd->{rra}[$ii]->{cdp_prep}[$j]->[VAL]) {
1221 19         67 $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         74 $rrd->{rra}[$ii]->{cdp_prep}[$j]->[PRIMARY_VAL] = $pdp_temp[$j];
1232 40 50       112 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         116 for (my $scratch_idx=PRIMARY_VAL; $rra_step_cnt[$ii] >0; $rra_step_cnt[$ii]--, $scratch_idx=SECONDARY_VAL) {
1240 30         75 $rrd->{rra}[$ii]->{ptr} = ($rrd->{rra}[$ii]->{ptr}+1) % $rrd->{rra}[$ii]->{row_cnt};
1241             #write_RRA_row
1242 30         30 my @line;
1243 30         63 for ($j=0; $j<$rrd->{ds_cnt}; $j++) {
1244 120         336 push(@line, $rrd->{rra}[$ii]->{cdp_prep}[$j]->[$scratch_idx]);
1245             }
1246 30 50       47 if ($inplace eq "memory") {
1247 30         71 $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         218 $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         35 $rrd->{last_up}=$current_time;
1259             } # args
1260 10 50       28 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         86 return 1;
1268             }
1269              
1270             sub fetch {
1271             # dump out measurement data
1272 1     1 1 1376 my ($self, $args_str) = @_; my $rrd=$self->{rrd};
  1         4  
1273 1         4 my $out='';
1274            
1275 1         3 my $step=$rrd->{pdp_step}; my $start=time()-24*60*60; my $end=time(); my $digits=10; # number of digits printed for floats
  1         8  
  1         4  
  1         2  
1276 1         2 my $ret; my $args;
1277 1         8 ($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       6 if ($start < 3600 * 24 * 365 * 10) {croak("the first entry to fetch should be after 1980");}
  0         0  
1285 1 50       5 if ($end < $start) {croak("start ($start) should be less than end ($end)");}
  0         0  
1286 1 50       5 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         3  
1288 1 50       12 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         2 my $cal_end; my $cal_start; my $step_diff; my $firstfull=1; my $firstpart=1;
  0         0  
  1         3  
  1         3  
1292 1         3 my $full_match=$end-$start;
1293 1         3 my $best_full_step_diff=0; my $best_full_rra; my $best_match=0;
  1         2  
  1         2  
1294 1         2 my $best_part_step_diff=0; my $best_part_rra;
  1         2  
1295             my $tmp_match;
1296 1         5 for ($i = 0; $i < $rrd->{rra_cnt}; $i++) {
1297 5 100       20 if ($rrd->{rra}[$i]->{name} eq $cf) {
1298 2         9 $cal_end=$rrd->{last_up} - $rrd->{last_up}%($rrd->{rra}[$i]->{pdp_cnt}*$rrd->{pdp_step});
1299 2         6 $cal_start=$cal_end - $rrd->{rra}[$i]->{pdp_cnt}*$rrd->{rra}[$i]->{row_cnt}*$rrd->{pdp_step};
1300 2         7 $step_diff = $step-$rrd->{pdp_step}*$rrd->{rra}[$i]->{pdp_cnt};
1301 2 100       7 if ($step_diff<0) {$step_diff=-$step_diff;} # take absolute value
  1         3  
1302 2 100       7 if ($cal_start <= $start) {
1303 1 50 33     6 if ($firstfull || $step_diff < $best_full_step_diff) {
1304 1         2 $firstfull=0; $best_full_step_diff = $step_diff; $best_full_rra=$i;
  1         2  
  1         3  
1305             }
1306             } else {
1307 1         3 $tmp_match = $full_match;
1308 1 50       5 if ($cal_start>$start) {$tmp_match-=($cal_start-$start);}
  1         2  
1309 1 50 0     6 if ($firstpart || ($best_match<$tmp_match && $step_diff < $best_part_step_diff)) {
      33        
1310 1         2 $firstpart=0; $best_match=$tmp_match; $best_part_step_diff=$step_diff; $best_part_rra=$i;
  1         3  
  1         1  
  1         4  
1311             }
1312             }
1313             }
1314             }
1315 1         1 my $chosen_rra; my @line;
1316 1 50       8 if ($firstfull == 0) {$chosen_rra=$best_full_rra;}
  1 0       2  
  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         3 $start -= $start % $step;
1321 1         3 $end += ($step - $end % $step);
1322              
1323             # load RRA data, if not already loaded
1324 1 50       6 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         22 $out.=sprintf "%-17s", $rrd->{ds}[$i]->{name};
1330             }
1331 1         3 $out.=sprintf "%s", "\n";
1332 1         7 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         2 my $jj; my $j;
1334 1         7 for ($j=0; $j<$rrd->{rra}[$chosen_rra]->{row_cnt}; $j++) {
1335 5 50 33     25 if ($t > $start && $t <= $end+$step) {
1336 5         13 $out.=sprintf "%10u: ",$t;
1337 5         12 $jj= ($rrd->{rra}[$chosen_rra]->{ptr}+1 + $j)%$rrd->{rra}[$chosen_rra]->{row_cnt};
1338 5         19 @line=$self->_unpackd($rrd->{rra}[$chosen_rra]->{data}[$jj]);
1339 5         17 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
1340 20         38 $out.=sprintf "%-17s",_strfloat($line[$i],$digits);
1341             }
1342 5         9 $out.=sprintf "%s", "\n";
1343             }
1344 5         16 $t+=$step;
1345             }
1346 1         13 return $out;
1347             }
1348              
1349             sub info {
1350             # dump out header info
1351 1     1 1 3 my $self=$_[0]; my $rrd = $self->{rrd};
  1         3  
1352 1         3 my $out='';
1353            
1354 1         2 my $digits=10; my $noencoding=0;
  1         2  
1355 1 50       4 if (defined($_[1])) {
1356 1         2 my $ret; my $args;
1357 1         6 ($ret, $args) = _GetOptionsFromString($_[1],
1358             "digits|d:i" => \$digits,
1359             "noformat|n" => \$noencoding
1360             );
1361             }
1362            
1363 1         8 $out.=sprintf "%s", "rrd_version = ".$rrd->{version}."\n";
1364 1 50       6 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         7 $out.=sprintf "%s", "step = ".$rrd->{pdp_step}."\n";
1376 1         5 $out.=sprintf "%s", "last_update = ".int($rrd->{last_up})."\n";
1377 1         2 my $i; my $str; my $ii;
  0         0  
1378 1         5 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
1379 4         11 $str="ds[".$rrd->{ds}[$i]->{name}."]";
1380 4         11 $out.=sprintf "%s", "$str.index = ".$i."\n";
1381 4         13 $out.=sprintf "%s", "$str.type = \"".$rrd->{ds}[$i]->{type}."\"\n";
1382 4         15 $out.=sprintf "%s", "$str.minimal_heartbeat = ".$rrd->{ds}[$i]->{hb}."\n";
1383 4         12 $out.=sprintf "%s.min = %s\n",$str,_strint($rrd->{ds}[$i]->{min});
1384 4         12 $out.=sprintf "%s.max = %s\n",$str,_strint($rrd->{ds}[$i]->{max});
1385 4         14 $out.=sprintf "%s", "$str.last_ds = \"".$rrd->{ds}[$i]->{pdp_prep}->{last_ds}."\"\n";
1386 4         14 $out.=sprintf "%s.value = %s\n",$str,_strfloat($rrd->{ds}[$i]->{pdp_prep}->{val}, $digits);
1387 4         25 $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         8 $str="rra[$i]";
1391 5         16 $out.=sprintf "%s", "$str.cf = \"".$rrd->{rra}[$i]->{name}."\"\n";
1392 5         16 $out.=sprintf "%s", "$str.rows = ".$rrd->{rra}[$i]->{row_cnt}."\n";
1393 5         19 $out.=sprintf "%s", "$str.cur_row = ".$rrd->{rra}[$i]->{ptr}."\n";
1394 5         14 $out.=sprintf "%s", "$str.pdp_per_row = ".$rrd->{rra}[$i]->{pdp_cnt}."\n";
1395 5         13 $out.=sprintf "%s.xff = %s\n",$str,_strfloat($rrd->{rra}[$i]->{xff},$digits);
1396 5         21 for ($ii=0; $ii<$rrd->{ds_cnt}; $ii++) {
1397 20         63 $out.=sprintf "%s.cdp_prep[$ii].value = %s\n",$str,_strfloat($rrd->{rra}[$i]->{cdp_prep}[$ii]->[VAL],$digits);
1398 20         129 $out.=sprintf "%s", "$str.cdp_prep[$ii].unknown_datapoints = ".$rrd->{rra}[$i]->{cdp_prep}[$ii]->[UNKN_PDP_CNT]."\n";
1399             }
1400             }
1401 1         28 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 12 my ($self, $args_str) = @_; my $rrd=$self->{rrd};
  4         11  
1412              
1413 4         11 my $noheader=0; my $notimecomments=0; my $digits=10;
  4         7  
  4         7  
1414 4 50       22 if (defined($args_str)) {
1415 4         8 my $ret; my $args;
1416 4         22 ($ret, $args) = _GetOptionsFromString($args_str,
1417             "no-header|n" => \$noheader,
1418             "notimecomments|t" => \$notimecomments,
1419             "digits|d:i" => \$digits
1420             );
1421             }
1422 4 50       22 my $timecomments = $notimecomments>0 ? 0 : 1;
1423            
1424             # load RRA data, if not already loaded
1425 4 100       20 if (!defined($rrd->{dataloaded})) {$self->_loadRRAdata;}
  1         6  
1426              
1427 4         8 my $out=''; my @line;
  4         10  
1428            
1429 4 50       17 if ($noheader<1) {
1430 4         12 $out.=sprintf "%s", ''."\n";
1431 4         12 $out.=sprintf "%s", ''."\n";
1432             }
1433 4         27 $out.=sprintf "%s", "\n\n\t".$rrd->{version}."\n";
1434 4         44 $out.=sprintf "%s", "\t".$rrd->{pdp_step}." \n\t".$rrd->{last_up}."";
1435 4 50       19 if ($timecomments) {$out.=" ";}
  0         0  
1436 4         8 $out.="\n\t";
1437 4         8 my $i; my $ii; my $j; my $jj; my $t; my $val;
  0         0  
  0         0  
  0         0  
  0         0  
1438 4         21 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
1439 16         59 $out.=sprintf "%s", "\n\t\n\t\t".$rrd->{ds}[$i]->{name}."\n\t\t";
1440 16         54 $out.=sprintf "%s", "".$rrd->{ds}[$i]->{type}."\n\t\t";
1441 16         57 $out.=sprintf "%s", "".$rrd->{ds}[$i]->{hb}."\n\t\t";
1442 16         54 $out.=sprintf "%s\n\t\t%s\n\t\t",_strint($rrd->{ds}[$i]->{min}),_strint($rrd->{ds}[$i]->{max});
1443 16         71 $out.=sprintf "%s", "\n\t\t\n\t\t".$rrd->{ds}[$i]->{pdp_prep}->{last_ds}."\n\t\t";
1444 16         55 $out.=sprintf "%s\n\t\t",_strfloat($rrd->{ds}[$i]->{pdp_prep}->{val},$digits);
1445 16         82 $out.=sprintf "%s", "".$rrd->{ds}[$i]->{pdp_prep}->{unkn_sec_cnt}."\n\t";
1446 16         57 $out.=sprintf "%s", "\n";
1447             }
1448 4         10 $out.=sprintf "%s", "\n\t\n";
1449 4         21 for ($i=0; $i<$rrd->{rra_cnt}; $i++) {
1450 20         28 $out.=sprintf "%s", "\t\n\t\t";
1451 20         63 $out.=sprintf "%s", "".$rrd->{rra}[$i]->{name}."\n\t\t";
1452 20         109 $out.=sprintf "%s", "".$rrd->{rra}[$i]->{pdp_cnt}." \n\n\t\t";
1453 20         200 $out.=sprintf "\n\t\t%s\n\t\t\n\t\t",_strfloat($rrd->{rra}[$i]->{xff},$digits);
1454 20         45 $out.=sprintf "%s", "\n\t\t";
1455 20         69 for ($ii=0; $ii<$rrd->{ds_cnt}; $ii++) {
1456 80         244 $out.=sprintf "\t\n\t\t\t%s\n\t\t\t", _strfloat($rrd->{rra}[$i]->{cdp_prep}[$ii]->[PRIMARY_VAL],$digits);
1457 80         744 $out.=sprintf "%s\n\t\t\t", _strfloat($rrd->{rra}[$i]->{cdp_prep}[$ii]->[SECONDARY_VAL],$digits);
1458 80         447 $out.=sprintf "%s\n\t\t\t",_strfloat($rrd->{rra}[$i]->{cdp_prep}[$ii]->[VAL],$digits);
1459 80         349 $out.=sprintf "%s", "". $rrd->{rra}[$i]->{cdp_prep}[$ii]->[UNKN_PDP_CNT]."\n\t\t\t";
1460 80         244 $out.=sprintf "%s", "\n\t\t";
1461             }
1462 20         28 $out.=sprintf "%s", "\n\t\t";
1463 20         23 $out.=sprintf "%s", "\n\t\t";
1464 20         96 $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         68 for ($j=0; $j<$rrd->{rra}[$i]->{row_cnt}; $j++) {
1466 120         299 $jj= ($rrd->{rra}[$i]->{ptr}+1 + $j)%$rrd->{rra}[$i]->{row_cnt};
1467 120 50       239 if ($timecomments) {$out.=sprintf "\t%s", " ";}
  0         0  
1468 120         173 $out.="";
1469 120         363 @line=$self->_unpackd($rrd->{rra}[$i]->{data}[$jj]);
1470 120         351 for ($ii=0; $ii<$rrd->{ds_cnt}; $ii++) {
1471 480         895 $out.=sprintf "%s",_strfloat($line[$ii],$digits);
1472             }
1473 120         165 $out.=sprintf "%s", "\n\t\t";
1474 120         518 $t+=$rrd->{rra}[$i]->{pdp_cnt}*$rrd->{pdp_step};
1475             }
1476 20         34 $out.=sprintf "%s", "\n\t";
1477 20         62 $out.=sprintf "%s", "\n";
1478             }
1479 4         13 $out.=sprintf "%s", "\n";
1480 4         102 return $out;
1481             }
1482              
1483             ####
1484             sub _saveheader {
1485             # construct binary header for RRD file
1486 3     3   8 my $self=$_[0];
1487 3         7 my $fd=$_[1];
1488              
1489 3         15 my $L=$self->_packlongchar();
1490 3         14 my $header="\0"x $self->_get_header_size; # preallocate header
1491 3         14 substr($header,0,9,"RRD\0".$self->{rrd}->{version});
1492 3         31 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         9 my $idx=$self->{STAT_HEADER_SIZE0};
1495 3         28 for (my $i=0; $i<$self->{rrd}->{ds_cnt}; $i++) {
1496 12         90 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         20 $idx+=40+$self->{FLOAT_EL_SIZE};
1499 12         45 my @minmax=($self->{rrd}->{ds}[$i]->{min}, $self->{rrd}->{ds}[$i]->{max});
1500 12         41 substr($header,$idx,2*$self->{FLOAT_EL_SIZE},$self->_packd(\@minmax));
1501 12         55 $idx+=9*$self->{FLOAT_EL_SIZE};
1502             }
1503             # RRA defs
1504 3         6 my $i;
1505 3         17 for ($i=0; $i<$self->{rrd}->{rra_cnt}; $i++) {
1506 15         106 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         30 $idx+=20+$self->{RRA_DEL_PAD}+2*$self->{LONG_EL_SIZE};
1508 15         86 my @xff=($self->{rrd}->{rra}[$i]->{xff});
1509 15         47 substr($header,$idx+$self->{RRA_PAD},$self->{FLOAT_EL_SIZE},$self->_packd(\@xff));
1510 15         71 $idx += $self->{FLOAT_EL_SIZE}*10+$self->{RRA_PAD};
1511             }
1512             # live header
1513 3         17 substr($header,$idx,2*$self->{LONG_EL_SIZE},pack("$L $L", $self->{rrd}->{last_up},0));
1514 3         6 $idx+= 2*$self->{LONG_EL_SIZE};
1515             # PDP_PREP
1516 3         15 for ($i=0; $i<$self->{rrd}->{ds_cnt}; $i++) {
1517 12         84 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         22 $idx+=30+$self->{PDP_PREP_PAD}+$self->{FLOAT_EL_SIZE};
1520 12         31 my @val=($self->{rrd}->{ds}[$i]->{pdp_prep}->{val});
1521 12         35 substr($header,$idx,$self->{FLOAT_EL_SIZE},$self->_packd(\@val));
1522 12         52 $idx+= $self->{FLOAT_EL_SIZE}*9;
1523             }
1524             # CDP_PREP
1525 3         8 my @val; my $ii;
1526 3         14 for (my $ii=0; $ii<$self->{rrd}->{rra_cnt}; $ii++) {
1527 15         40 for ($i=0; $i<$self->{rrd}->{ds_cnt}; $i++) {
1528             # do a bit of code optimisation here
1529 60 50 33     275 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         90 substr($header,$idx,$self->{FLOAT_EL_SIZE},$self->_packd([@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[0]]));
  60         316  
1537 60         129 $idx+=$self->{FLOAT_EL_SIZE};
1538 60         118 substr($header,$idx,$self->{FLOAT_EL_SIZE},pack("$L x".$self->{DIFF_SIZE},@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[1]));
  60         179  
1539 60         85 $idx+=$self->{FLOAT_EL_SIZE};
1540 60         96 substr($header,$idx,4*$self->{FLOAT_EL_SIZE},$self->_packd([@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[2..5]]));
  60         233  
1541 60         130 $idx+=4*$self->{FLOAT_EL_SIZE};
1542 60         160 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         194  
1543 60         92 $idx+=2*$self->{FLOAT_EL_SIZE};
1544 60         84 substr($header,$idx,2*$self->{FLOAT_EL_SIZE},$self->_packd([@val=@{$self->{rrd}->{rra}[$ii]->{cdp_prep}[$i]}[8..9]]));
  60         323  
1545 60         269 $idx+=2*$self->{FLOAT_EL_SIZE};
1546             }
1547             }
1548             }
1549             # RRA PTR
1550 3         32 for ($i=0; $i<$self->{rrd}->{rra_cnt}; $i++) {
1551 15         46 substr($header,$idx,$self->{LONG_EL_SIZE},pack("$L",$self->{rrd}->{rra}[$i]->{ptr}));
1552 15         39 $idx+=$self->{LONG_EL_SIZE};
1553             }
1554             #return $header;
1555 3         43 print $fd $header;
1556             }
1557              
1558             sub save {
1559             # save RRD data to a file
1560 3     3 1 1257 my $self=$_[0];
1561            
1562             # load RRA data, if not already loaded
1563 3 100       16 if (!defined($self->{rrd}->{dataloaded})) {$self->_loadRRAdata;}
  1         6  
1564            
1565 3 50       18 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   262 open $self->{fd}, "+<", $self->{file_name} or open $self->{fd}, ">", $self->{file_name} or croak "Couldn't open file ".$self->{file_name}.": $!\n";
  2         25  
  2         4  
  2         18  
1572 3         3584 binmode($self->{fd});
1573 3         8 my $fd=$self->{fd};
1574              
1575 3 50       15 if (!defined($self->{encoding})) { croak("Current encoding must be defined\n.");}
  0         0  
1576 3         5 my $current_encoding=$self->{encoding};
1577 3 50       14 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         15 $self->_sizes;
1584              
1585             # output headers
1586             #print $fd $self->getheader();
1587 3         18 $self->_saveheader($fd);
1588              
1589             # output data
1590 3         7 my @line; my $i; my $ii;
  0         0  
1591 3         15 for ($ii=0; $ii<$self->{rrd}->{rra_cnt}; $ii++) {
1592 15         48 for ($i=0; $i<$self->{rrd}->{rra}[$ii]->{row_cnt}; $i++) {
1593 75 50       132 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         303 print $fd $self->{rrd}->{rra}[$ii]->{data}[$i];
1599             }
1600             }
1601             # done
1602              
1603             # and exit
1604 3         46 return 1;
1605             }
1606              
1607             ####
1608             sub close {
1609             # close an open RRD file
1610 6     6 1 4075 my ($self) = @_;
1611 6 50       27 if (defined($self->{fd})) { close($self->{fd}); }
  6         110  
1612             }
1613              
1614             ####
1615              
1616             sub create {
1617             # create a new RRD
1618 1     1 1 535 my ($self, $args_str) = @_; my $rrd=$self->{rrd};
  1         7  
1619              
1620 1         5 my $last_up=time(); my $pdp_step=300;
  1         2  
1621 1         1 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       6 if ($last_up < 3600 * 24 * 365 * 10) { croak("the first entry to the RRD should be after 1980\n"); }
  0         0  
1629 1 50       3 if ($pdp_step <1) {croak("step size should be no less than one second\n");}
  0         0  
1630 1 50       9 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       4 if ($encoding =~ m/^native-double$/) {$encoding=_native_double();}
  0         0  
1632 1         4 $self->{encoding}=$encoding;
1633 1         7 $self->_sizes;
1634            
1635 1         2 $rrd->{version}="0003";
1636 1         3 $rrd->{ds_cnt}=0; $rrd->{rra_cnt}=0; $rrd->{pdp_step}=$pdp_step;
  1         2  
  1         2  
1637 1         2 $rrd->{last_up}=$last_up;
1638            
1639             # now parse the DS and RRA info
1640 1         1 my $i;
1641 0         0 my $min; my $max;
1642 1         3 for ($i=0; $i<@{$args}; $i++) {
  10         29  
1643 9 100       41 if (${$args}[$i] =~ m/^DS:([a-zA-Z0-9]+):(GAUGE|COUNTER|DERIVE|ABSOLUTE):([0-9]+):(U|[+|-]?[0-9\.]+):(U|[+|-]?[0-9\.]+)$/) {
  9 50       37  
  5         21  
1644 4         3 my $ds;
1645 4 50       7 $min=$4; if ($min eq "U") {$min=NAN;} # set to NaN
  4         15  
  4         5  
1646 4 50       6 $max=$5; if ($max eq "U") {$max=NAN;} # set to NaN
  4         7  
  4         5  
1647 4         36 ($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         8 $rrd->{ds}[@{$rrd->{ds}}]=$ds;
  4         7  
1651 4         8 $rrd->{ds_cnt}++;
1652             } elsif (${$args}[$i] =~ m/^RRA:(AVERAGE|MAX|MIN|LAST):([0-9\.]+):([0-9]+):([0-9]+)$/) {
1653 5         6 my $rra;
1654 5 50       11 if ($4<1) { croak("Invalid row count $4\n");}
  0         0  
1655 5 50 33     25 if ($2<0.0 || $2>1.0) { croak("Invalid xff $2: must be between 0 and 1\n");}
  0         0  
1656 5 50       10 if ($3<1) { croak("Invalid step $3: must be >= 1\n");}
  0         0  
1657 5         74 ($rra->{name}, $rra->{xff}, $rra->{pdp_cnt}, $rra->{row_cnt}, $rra->{ptr}, $rra->{data})=($1,$2,$3,$4,int(rand($4)),undef);
1658 5         7 $rrd->{rra}[@{$rrd->{rra}}]=$rra;
  5         8  
1659 5         9 $rrd->{rra_cnt}++;
1660             }
1661             }
1662 1 50       4 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         2 my $ii;
1666 1         4 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
1667 5         11 for ($i=0; $i<$rrd->{ds_cnt}; $i++) {
1668 20         36 @{$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         86  
1669             }
1670             }
1671            
1672             # initialise the data
1673 1         1 my $j;
1674 1         3 my @empty=((NAN)x$rrd->{ds_cnt});
1675 1         8 for ($ii=0; $ii<$rrd->{rra_cnt}; $ii++) {
1676 5         16 for ($i=0; $i<$rrd->{rra}[$ii]->{row_cnt}; $i++) {
1677 25         49 $rrd->{rra}[$ii]->{data}[$i]=$self->_packd(\@empty);
1678             }
1679             }
1680 1         14 $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 3279 my $self = $_[0]; my $rrd=$self->{rrd};
  6         27  
1687 6         17 $self->{file_name}=$_[1];
1688            
1689 6 50       389 open($self->{fd}, "<", $self->{file_name}) or croak "Couldn't open file ".$self->{file_name}.": $!\n";
1690 6         23 binmode($self->{fd});
1691 6         287 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         168 read($self->{fd},my $staticheader,16+8*NATIVE_DOUBLE_EL_SIZE);
1696 6         43 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     33 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         16 $self->{encoding}=undef;
1705 6         43 (my $x, my $y, my $t) =unpack("Z4 Z5 x![L!] d",substr($staticheader,0,length($staticheader)));
1706 6         104 my $file_floatcookie_native_double_simple = sprintf("%0.6e", $t);
1707 6         35 ($x, $y, $t) =unpack("Z4 Z5 x![d] d",substr($staticheader,0,length($staticheader)));
1708 6         36 my $file_floatcookie_native_double_mixed = sprintf("%0.6e", $t);
1709 6         41 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_SINGLE_EL_SIZE),"native-single");
1710 6         44 my $file_floatcookie_native_single=sprintf("%0.6e",$t);
1711 6         25 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_SINGLE_EL_SIZE),"portable-single");
1712 6         44 my $file_floatcookie_portable_single=sprintf("%0.6e",$t);
1713 6         25 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_DOUBLE_EL_SIZE),"portable-double");
1714 6         46 my $file_floatcookie_portable_double=sprintf("%0.6e",$t);
1715 6         12 my $file_floatcookie_littleendian_single;
1716             my $file_floatcookie_littleendian_double;
1717 6 50       24 if ($PACK_LITTLE_ENDIAN_SUPPORT>0) {
1718 6         104 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_SINGLE_EL_SIZE),"littleendian-single");
1719 6         37 $file_floatcookie_littleendian_single=sprintf("%0.6e",$t);
1720 6         26 ($t)=$self->_unpackd(substr($staticheader,12,PORTABLE_DOUBLE_EL_SIZE),"littleendian-double");
1721 6         41 $file_floatcookie_littleendian_double=sprintf("%0.6e",$t);
1722             }
1723 6         13 my $cookie=sprintf("%0.6e",DOUBLE_FLOATCOOKIE);
1724 6         14 my $singlecookie=sprintf("%0.6e",SINGLE_FLOATCOOKIE);
1725 6 50 33     73 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         16 $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         26 $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         52 ($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         71 seek $self->{fd},0,0; # go back to start of the file
1753 6         34 read($self->{fd},my $header,$self->_get_header_size);
1754             # extract header info into structured arrays
1755 6         17 my $pos=$self->{DS_DEF_IDX};
1756 6         24 $self->_extractDSdefs(\$header,$pos);
1757            
1758 6         16 $pos+=$self->{DS_EL_SIZE}*$rrd->{ds_cnt};
1759 6         26 $self->_extractRRAdefs(\$header,$pos);
1760            
1761 6         13 $pos+=$self->{RRA_DEF_EL_SIZE}*$rrd->{rra_cnt};
1762 6         29 $rrd->{last_up} = unpack("$L",substr($header,$pos,$self->{LONG_EL_SIZE}));
1763            
1764 6         14 $pos+=$self->{LIVE_HEAD_SIZE};
1765 6         46 $self->_extractPDPprep(\$header,$pos);
1766            
1767 6         15 $pos+=$self->{PDP_PREP_EL_SIZE}*$rrd->{ds_cnt};
1768 6         28 $self->_extractCDPprep(\$header,$pos);
1769            
1770 6         20 $pos+=$self->{CDP_PREP_EL_SIZE}*$rrd->{ds_cnt}*$rrd->{rra_cnt};
1771 6         31 $self->_extractRRAptr(\$header,$pos);
1772            
1773 6         15 $pos+=$self->{RRA_PTR_EL_SIZE}*$rrd->{rra_cnt};
1774            
1775             # validate file size
1776 6         10 my $i; my $row_cnt=0;
  6         14  
1777 6         24 for ($i=0; $i<$rrd->{rra_cnt}; $i++) {
1778 30         75 $row_cnt+=$rrd->{rra}[$i]->{row_cnt};
1779             }
1780 6         23 my $correct_len=$self->_get_header_size +$self->{FLOAT_EL_SIZE} * $row_cnt*$rrd->{ds_cnt};
1781 6 50 33     42 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         45 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.17
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 2013 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__