1package RRDTool::OO;
2
3use 5.6.0;
4use strict;
5use warnings;
6use Carp;
7use RRDs;
8use Storable;
9use Data::Dumper;
10use Log::Log4perl qw(:easy);
11
12our $VERSION = '0.36';
13
14   # Define the mandatory and optional parameters for every method.
15our $OPTIONS = {
16    new        => { mandatory => ['file'],
17                    optional  => [qw(raise_error dry_run strict)],
18                  },
19    create     => { mandatory => [qw(data_source)],
20                    optional  => [qw(step start hwpredict archive)],
21                    data_source => {
22                      mandatory => [qw(name type)],
23                      optional  => [qw(min max heartbeat)],
24                    },
25                    archive     => {
26                      mandatory => [qw(rows)],
27                      optional  => [qw(cfunc cpoints xff)],
28                    },
29                    hwpredict   => {
30                      mandatory => [qw(rows)],
31                      optional  => [qw(
32                                       alpha beta gamma
33                                       seasonal_period
34                                       threshold window_length
35                                      )],
36                    },
37                  },
38    update     => { mandatory => [qw()],
39                    optional  => [qw(time value values)],
40                  },
41    graph      => { mandatory => [qw(image)],
42                    optional  => [qw(vertical_label title start end x_grid
43                                     y_grid alt_y_grid no_minor alt_y_mrtg
44                                     alt_autoscale alt_autoscale_max base
45                                     units_exponent units_length width
46                                     height interlaced imginfo imgformat
47                                     overlay unit lazy upper_limit lower_limit
48                                     rigid
49                                     logarithmic color no_legend only_graph
50                                     force_rules_legend title step draw
51                                     line area shift tick
52                                     print gprint vrule hrule comment font
53                                     no_gridfit font_render_mode
54                                     font_smoothing_threshold slope_mode
55                                     tabwidth units watermark zoom
56                                     disable_rrdtool_tag
57                                    )],
58                    draw      => {
59                      mandatory => [qw()],
60                      optional  => [qw(file dsname cfunc thickness
61                                       type color legend name cdef vdef
62                                       stack step start end
63                                      )],
64                    },
65                    color     => {
66                      mandatory => [qw()],
67                      optional  => [qw(back canvas shadea shadeb
68                                       grid mgrid font frame arrow)],
69                    },
70                    font      => {
71                      mandatory => [qw(name)],
72                      optional  => [qw(element size)],
73                    },
74                    print      => {
75                      mandatory => [qw()],
76                      optional  => [qw(draw format cfunc)],
77                    },
78                    gprint     => {
79                      mandatory => [qw(format)],
80                      optional  => [qw(draw cfunc)],
81                    },
82                    vrule      => {
83                      mandatory => [qw(time)],
84                      optional  => [qw(color legend)],
85                    },
86                    hrule      => {
87                      mandatory => [qw(value)],
88                      optional  => [qw(color legend)],
89                    },
90                    comment    => {
91                      mandatory => [],
92                      optional  => [],
93                    },
94                    line        => {
95                      mandatory => [qw(value)],
96                      optional  => [qw(width color legend stack)],
97                    },
98                    area        => {
99                      mandatory => [qw(value)],
100                      optional  => [qw(color legend stack)],
101                    },
102                    tick        => {
103                      mandatory => [qw()],
104                      optional  => [qw(draw color legend fraction)],
105                    },
106                    shift       => {
107                      mandatory => [qw(offset)],
108                      optional  => [qw(draw)],
109                    },
110                 },
111     xport => {
112        mandatory => [qw(xport)],
113        optional  => [qw(def cdef start end step maxrows daemon)],
114        def => {
115            mandatory => [qw(file vname dsname cfunc)],
116            optional => [],
117        },
118        cdef => {
119            mandatory => [qw(vname rpn)],
120            optional => [],
121        },
122        xport => {
123            mandatory => [qw(vname)],
124            optional => [qw(legend)],
125        },
126    },
127    fetch_start=> { mandatory => [qw()],
128                    optional  => [qw(cfunc start end resolution)],
129                  },
130    fetch_next => { mandatory => [],
131                    optional  => [],
132                  },
133    dump       => { mandatory => [],
134                    optional  => [],
135                  },
136    restore    => { mandatory => [qw()],
137                    optional  => [qw(xml range_check)],
138                  },
139    tune       => { mandatory => [],
140                    optional  => [qw(heartbeat minimum maximum
141                                     type name)],
142                  },
143    first      => { mandatory => [],
144                    optional  => [],
145                  },
146    last       => { mandatory => [],
147                    optional  => [],
148                  },
149    info       => { mandatory => [],
150                    optional  => [],
151                  },
152    rrdresize  => { mandatory => [],
153                    optional  => [],
154                  },
155    rrdcgi     => { mandatory => [],
156                    optional  => [],
157                  },
158};
159
160my %RRDs_functions = (
161    create    => \&RRDs::create,
162    fetch     => \&RRDs::fetch,
163    update    => \&RRDs::update,
164    updatev   => \&RRDs::updatev,
165    graph     => \&RRDs::graph,
166    graphv    => \&RRDs::graphv,
167    info      => \&RRDs::info,
168    dump      => \&RRDs::dump,
169    restore   => \&RRDs::restore,
170    tune      => \&RRDs::tune,
171    first     => \&RRDs::first,
172    last      => \&RRDs::last,
173    info      => \&RRDs::info,
174    rrdresize => \&RRDs::rrdresize,
175    xport     => \&RRDs::xport,
176    rrdcgi    => \&RRDs::rrdcgi,
177);
178
179#################################################
180sub option_add {
181#################################################
182    my($self, $method, @options) = @_;
183
184    my @parts = split m#/#, $method;
185    my $ref = $OPTIONS;
186    $ref = $ref->{$_} for @parts;
187
188    push @{ $ref->{optional} }, $_ for @options;
189}
190
191#################################################
192sub check_options {
193#################################################
194    my($self, $method, $options) = @_;
195
196    $options = [] unless defined $options;
197
198    my %options_hash = (@$options);
199
200    my @parts = split m#/#, $method;
201
202    my $ref = $OPTIONS;
203
204    $ref = $ref->{$_} for @parts;
205
206    my %optional  = map { $_ => 1 } @{$ref->{optional}};
207    my %mandatory = map { $_ => 1 } @{$ref->{mandatory}};
208
209        # Check if we got all mandatory parameters
210    for(@{$ref->{mandatory}}) {
211        if(! exists $options_hash{$_}) {
212            Log::Log4perl->get_logger("")->logcroak(
213                "Mandatory parameter '$_' not set " .
214                "in $method() (@{[%mandatory]}) (@$options)");
215        }
216    }
217
218        # Check if all of the optional parameters we got are indeed
219        # valid optional parameters
220    if($self->{strict}) {
221        for(keys %options_hash) {
222            if(! exists $optional{$_} and
223               ! exists $mandatory{$_}) {
224                Log::Log4perl->get_logger("")->logcroak(
225                    "Illegal parameter '$_' in $method()");
226            }
227        }
228    }
229
230    1;
231}
232
233#################################################
234sub new {
235#################################################
236    my($class, %options) = @_;
237
238    my $self = {
239        raise_error        => 1,
240        strict             => 1,
241        dry_run            => 0,
242        exec_subref        => undef,
243        exec_args          => [],
244        exec_func          => [],
245        print_results      => [],
246        meta               =>
247            { discovered   => 0,
248              cfuncs       => [],
249              cfuncs_hash  => {},
250              dsnames      => [],
251              dsnames_hash => {},
252            },
253        %options,
254    };
255
256    bless $self, $class;
257
258      # For this one, we need to be strict
259    local $self->{strict} = 1;
260    $self->check_options("new", [%options]);
261
262    return $self;
263}
264
265#################################################
266sub first_def {
267#################################################
268    foreach(@_) {
269        return $_ if defined $_;
270    }
271    return undef;
272}
273
274#################################################
275sub create {
276#################################################
277    my($self, @options) = @_;
278
279    $self->check_options("create", \@options);
280    my %options_hash = @options;
281
282      # If it's a DateTime object, handle it gracefully
283    if( ref $options_hash{start} eq "DateTime" ) {
284        $options_hash{start} = $options_hash{start}->epoch();
285    }
286
287    my @archives;
288    my @data_sources;
289    my @hwpredict;
290
291    for(my $i=0; $i < @options; $i += 2) {
292          # Push copies (!) of original hashes onto internal data structures
293        push @archives, { %{$options[$i+1]} } if $options[$i] eq "archive";
294        push @hwpredict, { %{$options[$i+1]} } if $options[$i] eq "hwpredict";
295        push @data_sources,
296            { %{$options[$i+1]} } if $options[$i] eq "data_source";
297    }
298
299    if(!@archives and !@hwpredict) {
300        LOGDIE "No archives specified (use either 'archive' or 'hwpredict')";
301    }
302
303    DEBUG "Archives: ", scalar @archives, " Sources: ", scalar @data_sources;
304
305    for(@archives) {
306        $self->check_options("create/archive", [%$_]);
307    }
308    for(@data_sources) {
309        $self->check_options("create/data_source", [%$_]);
310    }
311    for(@hwpredict) {
312        $self->check_options("create/hwpredict", [%$_]);
313    }
314
315    my @rrdtool_options = ($self->{file});
316
317    push @rrdtool_options, "--start", $options_hash{start} if
318        exists $options_hash{start};
319
320    push @rrdtool_options, "--step", $options_hash{step} if
321        exists $options_hash{step};
322
323        # RRDtool default setting
324    $options_hash{step} ||= 300;
325
326    for(@data_sources) {
327       # DS:ds-name:DST:heartbeat:min:max
328       DEBUG "data_source: @{[%$_]}";
329       $_->{heartbeat} ||= $options_hash{step} * 2;
330       push @rrdtool_options,
331           "DS:$_->{name}:$_->{type}:$_->{heartbeat}:" .
332           (defined $_->{min} ? $_->{min} : "U") . ":" .
333           (defined $_->{max} ? $_->{max} : "U");
334
335       $self->meta_data("dsnames", $_->{name}, 1);
336    }
337
338    for(@archives) {
339       # RRA:CF:xff:steps:rows
340       DEBUG "archive: @{[%$_]}";
341       if(! exists $_->{xff}) {
342           $_->{xff} = 0.5;
343       }
344
345       $_->{cpoints} ||= 1;
346
347       if($_->{cpoints} > 1 and
348          !exists $_->{cfunc}) {
349           LOGDIE "Must specify cfunc if cpoints > 1";
350       }
351       if(! exists $_->{cfunc}) {
352           $_->{cfunc} = 'MAX';
353       }
354
355       $self->meta_data("cfuncs", $_->{cfunc}, 1);
356
357       push @rrdtool_options,
358           "RRA:$_->{cfunc}:$_->{xff}:$_->{cpoints}:$_->{rows}";
359    }
360
361    my $hwpredict_num = (scalar @archives) + 1;
362
363    for(@hwpredict) {
364      # RRA:HWPREDICT:rows:alpha:beta:seasonal period[:rra-num]
365      # RRA:SEASONAL:seasonal period:gamma:rra-num
366      # RRA:DEVSEASONAL:seasonal period:gamma:rra-num
367      # RRA:DEVPREDICT:rows:rra-num
368      # RRA:FAILURES:rows:threshold:window length:rra-num
369
370       DEBUG "hwpredict: @{[%$_]}";
371
372       def_or($_->{alpha}, 0.1);
373       def_or($_->{beta},  0.1);
374       def_or($_->{gamma}, $_->{alpha});
375       def_or($_->{threshold}, 7);
376       def_or($_->{window_length}, 9);
377       def_or($_->{seasonal_period}, int($_->{rows}/5) );
378
379#       push @rrdtool_options,
380#        "RRA:HWPREDICT:$_->{rows}:$_->{alpha}:" .
381#        "$_->{beta}:$_->{seasonal_period}:";
382
383         #0
384       push @rrdtool_options,
385        "RRA:HWPREDICT:$_->{rows}:$_->{alpha}:" .
386        "$_->{beta}:$_->{seasonal_period}:" .
387        ($hwpredict_num + 1);
388
389         #1
390       push @rrdtool_options,
391        "RRA:SEASONAL:$_->{seasonal_period}:$_->{gamma}:" .
392        ($hwpredict_num + 0);
393
394         #2
395       push @rrdtool_options,
396        "RRA:DEVSEASONAL:$_->{seasonal_period}:$_->{gamma}:" .
397        ($hwpredict_num + 0);
398
399         #3
400       push @rrdtool_options,
401        "RRA:DEVPREDICT:$_->{rows}:" .
402        ($hwpredict_num + 2);
403
404         #4
405       push @rrdtool_options,
406        "RRA:FAILURES:$_->{rows}:$_->{threshold}:" .
407        "$_->{window_length}:" .
408        ($hwpredict_num + 2);
409
410       $hwpredict_num++;
411    }
412
413    $self->RRDs_execute("create", @rrdtool_options);
414}
415
416#################################################
417sub RRDs_execute {
418#################################################
419    my ($self, $command, @args) = @_;
420
421    my $logger = get_logger("rrdtool");
422    $logger->info("rrdtool '$command' ", join " ", map { "'$_'" } @args);
423
424    if ($self->{dry_run}) {
425        $self->{exec_subref} = $RRDs_functions{$command} ;
426        $self->{exec_args}   = \@args ;
427        $self->{exec_func}   = $command;
428        return ;
429    }
430
431    my @rc;
432    my $error;
433
434    if(wantarray) {
435        @rc = $RRDs_functions{$command}->(@args);
436        INFO "rrdtool rc=(", array_as_string(\@rc), ")";
437        $error = 1 unless defined $rc[0];
438    } else {
439        $rc[0] = $RRDs_functions{$command}->(@args);
440        INFO "rrdtool rc=(", array_as_string(\@rc), ")";
441        $error = 1 unless $rc[0];
442    }
443
444    if($error) {
445        LOGDIE "rrdtool $command @args failed: ", $self->error_message() if
446            $self->{raise_error};
447    }
448
449        # Important to return no array in scalar context.
450    if(wantarray) {
451        return @rc;
452    } else {
453        return $rc[0];
454    }
455}
456
457#################################################
458sub get_exec_env {
459#################################################
460    my($self) = @_;
461
462    # returns stored environment in previous dry-run exec
463    return ($self->{exec_subref},
464            $self->{exec_args},
465            $self->{exec_func},
466           );
467}
468
469#################################################
470sub update {
471#################################################
472    my($self, @options) = @_;
473
474        # Expand short form
475    @options = (value => $options[0]) if @options == 1;
476
477    $self->check_options("update", \@options);
478
479    my %options_hash = @options;
480
481    $options_hash{time} = "N" unless exists $options_hash{time};
482
483      # If it's a DateTime object, handle it gracefully
484    if( ref $options_hash{time} eq "DateTime" ) {
485        $options_hash{time} = $options_hash{time}->epoch();
486    }
487
488    my $update_string  = "$options_hash{time}:";
489    my @update_options = ();
490
491    if(exists $options_hash{values}) {
492        if(ref($options_hash{values}) eq "HASH") {
493                # Do the template magic
494            push @update_options, "--template",
495                 join(":", keys %{$options_hash{values}});
496            $update_string .= join ":", values %{$options_hash{values}};
497        } else {
498                # We got multiple values in correct order
499            $update_string .= join ":", @{$options_hash{values}};
500        }
501    } else {
502            # We just have a single value
503        $update_string .= $options_hash{value};
504    }
505
506    my $caller = (caller(1))[3] ? (caller(1))[3] : '';
507    my $updatecmd = $caller eq __PACKAGE__."::updatev" ? 'updatev' : 'update';
508    my ($print_results) =
509        $self->RRDs_execute($updatecmd, $self->{file},
510                            @update_options, $update_string);
511
512    if(!defined $print_results) {
513        return undef;
514    }
515
516    $self->print_results( $print_results );
517
518    return 1;
519}
520
521#################################################
522sub updatev {
523#################################################
524    &update (@_);
525}
526
527#################################################
528sub fetch_start {
529#################################################
530    my($self, @options) = @_;
531
532    $self->check_options("fetch_start", \@options);
533
534    my %options_hash = @options;
535
536    if(!exists $options_hash{cfunc}) {
537        my $cfuncs = $self->meta_data("cfuncs");
538        LOGDIE "No default archive cfunc" unless
539            defined $cfuncs->[0];
540        $options_hash{cfunc} = $cfuncs->[0];
541        DEBUG "Getting default cfunc '$options_hash{cfunc}'";
542    }
543
544    my $cfunc = $options_hash{cfunc};
545    delete $options_hash{cfunc};
546
547    @options = add_dashes(\%options_hash);
548
549    INFO "rrdtool fetch $self->{file} $cfunc @options";
550
551    ($self->{fetch_time_current},
552     $self->{fetch_time_step},
553     $self->{fetch_ds_names},
554     $self->{fetch_data}) =
555         $self->RRDs_execute("fetch", $self->{file}, $cfunc, @options);
556
557    $self->{fetch_idx} = 0;
558}
559
560#################################################
561sub fetch_next {
562#################################################
563    my($self) = @_;
564
565    if(!defined $self->{fetch_data}->[$self->{fetch_idx}]) {
566        INFO "Idx $self->{fetch_idx} returned undef";
567        return ();
568    }
569
570    my @values = @{$self->{fetch_data}->[$self->{fetch_idx}++]};
571
572        # Put the time of the data point in front
573    unshift @values, $self->{fetch_time_current};
574
575    INFO "rrdtool fetch $self->{file} ", array_as_string(\@values) if @values;
576
577    $self->{fetch_time_current} += $self->{fetch_time_step};
578
579    return @values;
580}
581
582#################################################
583sub array_as_string {
584#################################################
585    my($arrayref) = @_;
586
587    return join "-", map { defined $_ ? $_ : '[undef]' } @$arrayref;
588}
589
590#################################################
591sub fetch_skip_undef {
592#################################################
593    my($self) = @_;
594
595    {
596        if(!defined $self->{fetch_data}->[$self->{fetch_idx}]) {
597            return undef;
598        }
599
600        my $value = $self->{fetch_data}->[$self->{fetch_idx}]->[0];
601
602        unless(defined $value) {
603            $self->{fetch_idx}++;
604            $self->{fetch_time_current} += $self->{fetch_time_step};
605            redo;
606        }
607    }
608}
609
610#################################################
611sub add_dashes {
612#################################################
613    my($options_hashref, $assign_hashref) = @_;
614
615    $assign_hashref = {} unless $assign_hashref;
616
617    my @options = ();
618
619    foreach(keys %$options_hashref) {
620        (my $newname = $_) =~ s/_/-/g;
621        if($assign_hashref->{$_}) {
622            push @options, "--$newname=$options_hashref->{$_}";
623        } elsif(defined $options_hashref->{$_}) {
624            push @options, "--$newname", $options_hashref->{$_};
625        } else {
626            push @options, "--$newname";
627        }
628    }
629
630    return @options;
631}
632
633#################################################
634sub error_message {
635#################################################
636    my($self) = @_;
637
638    return RRDs::error();
639}
640
641#################################################
642sub graph {
643#################################################
644    my($self, @params) = @_;
645
646    my @options = @{ Storable::dclone( \@params ) };
647
648    my @trailing_options = ();
649
650    $self->check_options("graph", \@options);
651    $self->print_results( [] );
652
653    my @colors = ();
654    my @prints = ();
655    my @vrules = ();
656    my @hrules = ();
657    my @fonts  = ();
658
659    my @items = ();
660    my $nof_draws = 0;
661    my @draws = ();
662
663    my %options_hash = @options;
664    my $draw_count   = 1;
665
666    my $image = delete $options_hash{image};
667    delete $options_hash{draw};
668
669    for(my $i=0; $i < @options; $i += 2) {
670        if($options[$i] eq "draw") {
671            push @items, ['draw', $options[$i+1]];
672            push @draws, $options[$i+1];
673            $nof_draws++;
674        } elsif($options[$i] eq "color") {
675            $self->check_options("graph/color", [%{$options[$i+1]}]);
676            for(keys %{$options[$i+1]}) {
677                push @colors, "--color",
678                              uc($_) . "$options[$i+1]->{$_}";
679            }
680        } elsif($options[$i] eq "print") {
681            $self->check_options("graph/print", [%{$options[$i+1]}]);
682            push @items, ['print', [$options[$i], $options[$i+1]]];
683        } elsif($options[$i] eq "gprint") {
684            $self->check_options("graph/gprint", [%{$options[$i+1]}]);
685            push @items, ['print', [$options[$i], $options[$i+1]]];
686        } elsif($options[$i] eq "comment") {
687            push @items, ['print', option_expand(@options[$i, $i+1])];
688        } elsif($options[$i] eq "line") {
689            $self->check_options("graph/line", [%{$options[$i+1]}]);
690            push @items, ['print', option_expand(@options[$i, $i+1])];
691        } elsif($options[$i] eq "area") {
692            $self->check_options("graph/area", [%{$options[$i+1]}]);
693            push @items, ['print', option_expand(@options[$i, $i+1])];
694        } elsif($options[$i] eq "vrule") {
695            $self->check_options("graph/vrule", [%{$options[$i+1]}]);
696            push @items, ['vrule', [$options[$i], $options[$i+1]]];
697        } elsif($options[$i] eq "hrule") {
698            $self->check_options("graph/hrule", [%{$options[$i+1]}]);
699            push @items, ['hrule', [$options[$i], $options[$i+1]]];
700        } elsif($options[$i] eq "tick") {
701            $self->check_options("graph/tick", [%{$options[$i+1]}]);
702            push @items, ['print', option_expand(@options[$i, $i+1])];
703        } elsif($options[$i] eq "shift") {
704            $self->check_options("graph/shift", [%{$options[$i+1]}]);
705            push @items, ['print', option_expand(@options[$i, $i+1])];
706        } elsif($options[$i] eq "font") {
707            push @fonts,$options[$i+1];
708        }
709    }
710
711    delete $options_hash{color};
712    delete $options_hash{vrule};
713    delete $options_hash{hrule};
714    delete $options_hash{'print'};
715    delete $options_hash{gprint};
716    delete $options_hash{comment};
717    delete $options_hash{font};
718    delete $options_hash{line};
719    delete $options_hash{area};
720    delete $options_hash{tick};
721    delete $options_hash{'shift'};
722
723      # If it's a DateTime object, handle it gracefully
724    for my $o (qw(start end)) {
725        if( ref $options_hash{$o} eq "DateTime" ) {
726            $options_hash{$o} = $options_hash{$o}->epoch();
727        }
728    }
729
730    @options = add_dashes(\%options_hash);
731
732    # Set dsname default
733    if(!exists $options_hash{dsname}) {
734        my $dsname = $self->default("dsname");
735        LOGDIE "No default archive dsname" unless defined $dsname;
736        $options_hash{dsname} = $dsname;
737        DEBUG "Getting default dsname '$dsname'";
738    }
739
740    # Set cfunc default
741    if(!exists $options_hash{cfunc}) {
742        my $cfunc = $self->default("cfunc");
743        LOGDIE "No default archive cfunc" unless defined $cfunc;
744        $options_hash{cfunc} = $cfunc;
745        DEBUG "Getting default cfunc '$cfunc'";
746    }
747
748        # Push a pseudo draw if there's none.
749    unshift @items, ['draw', {}] unless $nof_draws;
750
751    for(@fonts) {
752
753        $self->check_options("graph/font", [%$_]);
754
755        $_->{size}     ||= 8;
756        $_->{element}  ||= 'default';
757        $_->{name}     ||= '';       # but this breaks.
758                                     # Need to issue an error eventually.
759
760        push @options,"--font", uc($_->{element}) . ":" .
761                                $_->{size} . ":" . $_->{name};
762    }
763
764    for my $item (@items) {
765        if($item->[0] eq 'draw') {
766            $self->process_draw($item->[1], \@options,
767                                \%options_hash, $draw_count);
768            $draw_count++;
769        } elsif($item->[0] eq 'vrule') {
770            $self->process_vrule($item->[1], \@options);
771        } elsif($item->[0] eq 'hrule') {
772            $self->process_hrule($item->[1], \@options);
773        } elsif($item->[0] eq 'print') {
774            for(@$item[1..$#$item]) {
775                $self->process_print($_, \@options, \@draws);
776            }
777        }
778    }
779
780    push @options, @colors;
781    unshift @options, $image;
782
783    my $caller = (caller(1))[3] ? (caller(1))[3] : '';
784    my $graphcmd = $caller eq __PACKAGE__."::graphv" ? 'graphv' : 'graph';
785    my($print_results, $img_width, $img_height) =
786        $self->RRDs_execute($graphcmd, @options);
787
788    if(!defined $print_results) {
789        return undef;
790    }
791
792    $self->print_results( $print_results );
793
794    return 1;
795}
796
797#################################################
798sub graphv {
799#################################################
800    &graph (@_);
801}
802
803###########################################
804sub print_results {
805###########################################
806    my($self, $results) = @_;
807
808    if(defined $results) {
809        $self->{results} = $results;
810    }
811
812    return $self->{results};
813}
814
815#################################################
816sub option_expand {
817#################################################
818    my($oname, $ovalue) = @_;
819
820    # If $ovalue is an array ref, return ($oname, $element)
821    # for each of the elements in @$ovalue.
822    my @result;
823
824    if ( ref($ovalue) eq 'ARRAY' ) {
825        push @result, [$oname, $_] foreach @$ovalue;
826    } else {
827        push @result, [$oname, $ovalue];
828    }
829
830    return @result;
831}
832
833#################################################
834sub dump {
835#################################################
836    my($self, @options) = @_;
837
838    $self->RRDs_execute("dump", $self->{file}, @options);
839}
840
841#################################################
842sub restore {
843#################################################
844    my($self, @options) = @_;
845
846        # Called with only the xml file
847    if(@options == 1) {
848        @options = (xml => $options[0]);
849    }
850
851    my %options_hash = @options;
852    my $xml = delete $options_hash{xml};
853
854    @options = add_dashes(\%options_hash);
855
856    $self->RRDs_execute("restore", $xml, $self->{file},
857                        @options);
858}
859
860#################################################
861sub tune {
862#################################################
863    my($self, @options) = @_;
864
865    my %options_hash = @options;
866
867    my $dsname = first_def $options_hash{dsname}, $self->default("dsname");
868    delete $options_hash{dsname};
869
870    @options = ();
871
872    my %map = qw( type data-source-type
873                  name data-source-rename
874                );
875
876    for my $param (qw(heartbeat minimum maximum type name)) {
877        if(exists $options_hash{$param}) {
878            my $newparam = $param;
879
880            $newparam = $map{$param} if exists $map{$param};
881
882            push @options, "--$newparam",
883                 "$dsname:$options_hash{$param}";
884        }
885    }
886
887    my $rc = $self->RRDs_execute("tune", $self->{file}, @options);
888
889    # This might impact the default dsname, rediscover
890    $self->meta_data_discover();
891
892    return $rc;
893}
894
895#################################################
896sub default {
897#################################################
898    my($self, $param) = @_;
899
900    if($param eq "cfunc") {
901        my $cfuncs = $self->meta_data("cfuncs");
902        return undef unless $cfuncs;
903            # Return the first of all defined consolidation functions
904        return $cfuncs->[0];
905    }
906
907    if($param eq "dsname") {
908        my $dsnames = $self->meta_data("dsnames");
909        return undef unless $dsnames;
910            # Return the first of all defined data sources
911        return $dsnames->[0];
912    }
913
914    return undef;
915}
916
917#################################################
918sub meta_data {
919#################################################
920    my($self, $field, $value, $unique_push) = @_;
921
922    if(defined $value) {
923        $self->{meta}->{discovered} = 1;
924    }
925
926    if(!$self->{meta}->{discovered}) {
927        $self->meta_data_discover();
928    }
929
930    if(defined $value) {
931        if($unique_push) {
932            push @{$self->{meta}->{$field}}, $value unless
933                   $self->{meta}->{"${field}_hash"}->{$value}++;
934        } else {
935            $self->{meta}->{$field} = $value;
936        }
937    }
938
939    return $self->{meta}->{$field};
940}
941
942#################################################
943sub meta_data_discover {
944#################################################
945    my($self) = @_;
946
947    #==========================================
948    # rrdtoo info output
949    #==========================================
950    #filename = "myrrdfile.rrd"
951    #rrd_version = "0001"
952    #step = 1
953    #last_update = 1084773097
954    #ds[mydatasource].type = "GAUGE"
955    #ds[mydatasource].minimal_heartbeat = 2
956    #ds[mydatasource].min = NaN
957    #ds[mydatasource].max = NaN
958    #ds[mydatasource].last_ds = "UNKN"
959    #ds[mydatasource].value = 0.0000000000e+00
960    #ds[mydatasource].unknown_sec = 0
961    #rra[0].cf = "MAX"
962    #rra[0].rows = 5
963    #rra[0].pdp_per_row = 1
964    #rra[0].xff = 5.0000000000e-01
965    #rra[0].cdp_prep[0].value = NaN
966    #rra[0].cdp_prep[0].unknown_datapoints = 0
967
968        # Nuke everything
969    delete $self->{meta};
970
971    my $hashref = $self->RRDs_execute("info", $self->{file});
972
973    foreach my $key (keys %$hashref){
974
975        if($key =~ /^rra\[\d+\]\.cf/) {
976            DEBUG "rrdinfo: rra found: $key";
977            $self->meta_data("cfuncs", $hashref->{$key}, 1);
978            next;
979        } elsif ($key =~ /^ds\[(.*?)]\./) {
980            DEBUG "rrdinfo: da found: $key";
981            $self->meta_data("dsnames", $1, 1);
982            next;
983        } else {
984            DEBUG "rrdinfo: no match: $key";
985        }
986    }
987
988    DEBUG "Discovery: cfuncs=(@{$self->{meta}->{cfuncs}}) ",
989                    "dsnames=(@{$self->{meta}->{dsnames}})";
990
991    $self->{meta}->{discovered} = 1;
992}
993
994#################################################
995sub info_aux {
996#################################################
997    my($self) = @_;
998
999    return $self->RRDs_execute("info", $self->{file});
1000}
1001
1002#################################################
1003sub info {
1004#################################################
1005    my($self) = @_;
1006
1007    my $hashref = $self->info_aux();
1008
1009        # Returns something like
1010          # {'rra[0].rows' => 5,
1011          # 'rra[1].pdp_per_row' => 5,
1012          # 'last_update' => 1080462600,
1013          # 'rra[0].cf' => 'MAX',
1014          # 'step' => 60,
1015          # 'rra[1].cdp_prep[0].value' => undef,
1016          # 'rra[0].cdp_prep[0].unknown_datapoints' => 0,
1017          # ...
1018          # }
1019        # Parse it into a Perl array/hash hierarchy:
1020
1021    my $h = {};
1022
1023    for my $key (keys %$hashref) {
1024
1025        my $ptr = \$h;
1026
1027        while($key =~ /\G(?:\.?(\w+)|\[(\d+)\]|\[(.*?)\])/g) {
1028            $ptr = $1         ? \$$ptr->{$1} :
1029                   defined $2 ? \$$ptr->[$2] :
1030                                \$$ptr->{$3};
1031        }
1032
1033        $$ptr = $hashref->{$key};
1034    }
1035
1036    return $h;
1037}
1038
1039#################################################
1040sub first {
1041#################################################
1042    my($self) = @_;
1043
1044    $self->RRDs_execute("first", $self->{file});
1045}
1046
1047#################################################
1048sub last {
1049#################################################
1050    my($self) = @_;
1051
1052    $self->RRDs_execute("last", $self->{file});
1053}
1054
1055###########################################
1056sub process_draw {
1057###########################################
1058    my($self, $p, $options, $options_hash, $draw_count) = @_;
1059
1060    $self->check_options("graph/draw", [%$p]);
1061
1062        $p->{thickness} ||= 1;        # LINE1 is default
1063        $p->{color}     ||= 'FF0000'; # red is default
1064        $p->{legend}    ||= '';       # no legend by default
1065
1066        $p->{file}   = first_def $p->{file}, $self->{file};
1067
1068        my($dsname, $cfunc);
1069
1070        if($p->{file} ne $self->{file}) {
1071            my $rrd = __PACKAGE__->new(file => $p->{file});
1072            $dsname = $rrd->default('dsname');
1073            $cfunc  = $rrd->default('cfunc');
1074        }
1075
1076        unless(defined $p->{name}) {
1077            $p->{name} = "draw$draw_count";
1078        }
1079
1080            # Is it just a CDEF, a different view of a another draw?
1081        if($p->{cdef}) {
1082            push @$options, "CDEF:$p->{name}=$p->{cdef}";
1083        } elsif($p->{vdef}) {
1084            push @$options, "VDEF:$p->{name}=$p->{vdef}";
1085        } else {
1086                # Use either directly defined, default for a given file or
1087                # default for default file, in this order.
1088            $p->{dsname} = first_def $p->{dsname}, $dsname,
1089                                     $options_hash->{dsname};
1090            $p->{cfunc}  = first_def $p->{cfunc}, $cfunc,
1091                                     $options_hash->{cfunc};
1092
1093            # Create the draw strings
1094            # DEF:vname=rrdfile:ds-name:CF[:step=step][:start=time][:end=time]
1095            my $def = "DEF:$p->{name}=$p->{file}:$p->{dsname}:$p->{cfunc}";
1096            map { $def .= ":$_=$p->{$_}" } grep { defined $p->{$_} } qw(step start end);
1097            push @$options, $def;
1098        }
1099
1100            #LINE2:myload#FF0000
1101        $p->{type} ||= 'line';
1102
1103        my $draw_attributes = ":$p->{name}#$p->{color}";
1104
1105        if( length $p->{legend} ) {
1106            $draw_attributes .= ":$p->{legend}";
1107        } elsif( exists $p->{stack} ) {
1108            $draw_attributes .= ":";
1109        }
1110
1111        $draw_attributes .= ":STACK" if exists $p->{stack};
1112
1113        if($p->{type} eq "hidden") {
1114            # Invisible graph
1115        } elsif($p->{type} eq "line") {
1116            push @$options, "LINE$p->{thickness}$draw_attributes";
1117        } elsif($p->{type} eq "area") {
1118            push @$options, "AREA$draw_attributes";
1119        } elsif($p->{type} eq "stack") {
1120            if( ! length $p->{legend} ) {
1121                $draw_attributes .= ":";
1122            }
1123              # modified for backwards compatibility
1124            push @$options, "AREA$draw_attributes:STACK";
1125        } else {
1126            die "Invalid graph type: $p->{type}";
1127        }
1128}
1129
1130###########################################
1131sub process_vrule {
1132###########################################
1133    my($self, $vrule, $options) = @_;
1134
1135    # Push vrules
1136    $vrule->[1]->{color} ||= "#000000";
1137    push @$options, uc($vrule->[0]) . ":" .
1138                    $vrule->[1]->{time} .
1139                    $vrule->[1]->{color} .
1140                    ( $vrule->[1]->{legend} ?
1141                         ":" . $vrule->[1]->{legend} : "");
1142}
1143
1144###########################################
1145sub process_hrule {
1146###########################################
1147    my($self, $hrule, $options) = @_;
1148
1149    # Push hrules
1150    $hrule->[1]->{color} ||= "#000000";
1151    push @$options, uc($hrule->[0]) . ":" .
1152                    $hrule->[1]->{value} .
1153                    $hrule->[1]->{color} .
1154                    ( $hrule->[1]->{legend} ?
1155                         ":" . $hrule->[1]->{legend} : "");
1156}
1157
1158###########################################
1159sub process_print {
1160###########################################
1161    my($self, $p, $options, $draws) = @_;
1162
1163    if ( $p->[0] eq 'comment' ) {
1164        push @$options, uc($p->[0]) . ":" . $p->[1];
1165
1166    } elsif( $p->[0] =~ /^(line)|(area)$/ ) {
1167        push @$options, uc($p->[0]) .
1168                       ($p->[1]->{width} || "") .
1169                       ":" .
1170                       $p->[1]->{value} .
1171                       ($p->[1]->{color} || "") .
1172                       ($p->[1]->{legend} ? ":$p->[1]->{legend}" : "") .
1173                       ($p->[1]->{stack} ? ":STACK" : "");
1174
1175    } elsif( $p->[0] eq "tick" ) {
1176        push @$options, uc($p->[0]) . ":" .
1177                   ($p->[1]->{draw} || $draws->[0]->{name}) .
1178                   ($p->[1]->{color} || '#ff0000') .
1179                   ($p->[1]->{fraction} ? ":$p->[1]->{fraction}" : ":.1") .
1180                   ($p->[1]->{legend} ? ":$p->[1]->{legend}" : "");
1181
1182    } elsif( $p->[0] eq "shift" ) {
1183        push @$options, uc($p->[0]) . ":" .
1184                       ($p->[1]->{draw} || $draws->[0]->{name}) .
1185                       ":$p->[1]->{offset}";
1186
1187    } else {
1188        $p->[1]->{draw}   ||= $draws->[0]->{name};
1189        $p->[1]->{format} ||= "Average=%lf";
1190        push @$options, uc($p->[0]) . ":" .
1191                       $p->[1]->{draw} . ":" .
1192                       ($p->[1]->{cfunc} ? "$p->[1]->{cfunc}:" : "") .
1193                       $p->[1]->{format};
1194    }
1195}
1196
1197#################################################
1198sub xport {
1199#################################################
1200	my ($this, @options) = @_;
1201
1202	my $sname = "xport";
1203	my $section = $OPTIONS->{$sname};
1204
1205	DEBUG(sub { Dumper($OPTIONS) });
1206	DEBUG(sub { Dumper($section) });
1207
1208	$this->check_options($sname, \@options);
1209	$this->print_results([]);
1210
1211	my %options = @options;
1212	my $ref;
1213	my @cmd;
1214	# If it's a DateTime object, handle it gracefully
1215	foreach (qw(start end)) {
1216		next unless exists($options{$_});
1217		next unless defined($options{$_});
1218		if (ref($options{$_}) eq "DateTime") {
1219			$options{$_} = $options{$_}->epoch();
1220		}
1221	}
1222
1223	my @all_options = (@{$section->{optional}}, @{$section->{mandatory}});
1224	foreach my $opt (@all_options) {
1225		DEBUG("Processing optional option '$opt'");
1226		if (defined($options{$opt}) and not ref($options{$opt})) {
1227			push(@cmd, "--$opt", $options{$opt});
1228			DEBUG("[xport] Pushed option '--$opt' with value '$options{$opt}'");
1229		}
1230	}
1231	undef(@all_options);
1232
1233	my %params = (
1234		def => [],
1235		cdef => [],
1236		xport => [],
1237	);
1238
1239	my $string;
1240	foreach my $sec (keys(%params)) {
1241		next unless (defined($options{$sec}));
1242		LOGDIE("$sec section must be an array ref") unless (ref($options{$sec}) eq "ARRAY");
1243		foreach my $opts (@{$options{$sec}}) {
1244			LOGDIE("$sec/$opts section must be a hash ref") unless (ref($opts) eq "HASH");
1245			my @opts = %$opts;
1246			$this->check_options("$sname/$sec", \@opts);
1247
1248			my $array = $params{$sec};
1249
1250			# DEF
1251			if ($sec =~ /^def$/i) {
1252				$string = "DEF:";
1253				$string .= "$opts->{vname}=";
1254				$string .= "$opts->{file}:";
1255				$string .= "$opts->{dsname}:";
1256				$string .= $opts->{cfunc};
1257				push(@$array, $string);
1258				DEBUG("[xport] Pushed DEF '$string'");
1259			}
1260			# CDEF
1261			elsif ($sec =~ /^cdef$/i) {
1262				$string = "CDEF:";
1263				$string .= "$opts->{vname}=";
1264				$string .= $opts->{rpn};
1265				push(@$array, $string);
1266				DEBUG("[xport] Pushed CDEF '$string'");
1267			}
1268			# XPORT
1269			else {
1270				$string = "XPORT:";
1271				$string .= $opts->{vname};
1272				$string .= ":$opts->{legend}" if defined($opts->{legend});
1273				push(@$array, $string);
1274				DEBUG("[xport] Pushed XPORT '$string'");
1275			}
1276		}
1277
1278	}
1279
1280	# Order matters !
1281	foreach my $sec (qw(def cdef xport)) {
1282		push(@cmd, @{$params{$sec}}) if (defined($params{$sec}) and scalar @{$params{$sec}} != 0);
1283	}
1284
1285	DEBUG("[xport] RRDs command: ".join(" ", @cmd));
1286
1287	my @results = $this->RRDs_execute($sname, @cmd);
1288	LOGDIE("RRDs::xport() failed") unless (scalar @results > 0);
1289
1290	my %meta_data = (
1291		start => $results[0], # Exactly start+step
1292		end => $results[1],
1293		step => $results[2],
1294		columns => $results[3],
1295		legend => $results[4],
1296	);
1297
1298	my $time = $meta_data{start};
1299
1300	my @data;
1301	foreach my $data (@{$results[5]}) {
1302		push(@data, [$time, @$data]);
1303		$time += $meta_data{step};
1304	}
1305
1306	$meta_data{rows} = scalar @data;
1307
1308    my $results = {
1309		meta => \%meta_data,
1310		data => \@data,
1311	};
1312
1313    return $this->print_results($results);
1314}
1315
1316
1317
1318##########################################
1319sub def_or($$) {
1320###########################################
1321    if(! defined $_[0]) {
1322        $_[0] = $_[1];
1323    }
1324}
1325
13261;
1327
1328__END__
1329
1330=head1 NAME
1331
1332RRDTool::OO - Object-oriented interface to RRDTool
1333
1334=head1 SYNOPSIS
1335
1336    use RRDTool::OO;
1337
1338        # Constructor
1339    my $rrd = RRDTool::OO->new(
1340                 file => "myrrdfile.rrd" );
1341
1342        # Create a round-robin database
1343    $rrd->create(
1344         step        => 1,  # one-second intervals
1345         data_source => { name      => "mydatasource",
1346                          type      => "GAUGE" },
1347         archive     => { rows      => 5 });
1348
1349        # Update RRD with sample values, use current time.
1350    for(1..5) {
1351        $rrd->update($_);
1352        sleep(1);
1353    }
1354
1355        # Start fetching values from one day back,
1356        # but skip undefined ones first
1357    $rrd->fetch_start();
1358    $rrd->fetch_skip_undef();
1359
1360        # Fetch stored values
1361    while(my($time, $value) = $rrd->fetch_next()) {
1362         print "$time: ",
1363               defined $value ? $value : "[undef]", "\n";
1364    }
1365
1366        # Draw a graph in a PNG image
1367    $rrd->graph(
1368      image          => "mygraph.png",
1369      vertical_label => 'My Salary',
1370      start          => time() - 10,
1371      draw           => {
1372          type   => "area",
1373          color  => '0000FF',
1374          legend => "Salary over Time",
1375      }
1376    );
1377
1378        # Same using rrdtool's graphv
1379    $rrd->graphv(
1380      image          => "mygraph.png",
1381      [...]
1382    };
1383
1384=head1 DESCRIPTION
1385
1386=for html
1387<IMG SRC=/images/rrdtool/mygraph.png>
1388
1389C<RRDTool::OO> is an object-oriented interface to Tobi Oetiker's
1390round robin database tool I<rrdtool>. It uses I<rrdtool>'s
1391C<RRDs> module to get access to I<rrdtool>'s shared library.
1392
1393C<RRDTool::OO> tries to marry I<rrdtool>'s database engine with the
1394dwimminess and whipuptitude Perl programmers take for granted. Using
1395C<RRDTool::OO> abstracts away implementation details of the RRD engine,
1396uses easy to memorize named parameters and sets meaningful defaults
1397for parameters not needed in simple cases.
1398For the experienced user, however, it provides full access to
1399I<rrdtool>'s API (if you find a feature that's not implemented, let
1400me know).
1401
1402=head2 FUNCTIONS
1403
1404=over 4
1405
1406=item I<my $rrd = RRDTool::OO-E<gt>new( file =E<gt> $file )>
1407
1408The constructor hooks up with an existing RRD database file C<$file>,
1409but doesn't create a new one if none exists. That's what the C<create()>
1410methode is for. Returns a C<RRDTool::OO> object, which can be used to
1411get access to the following methods.
1412
1413=item I<$rrd-E<gt>create( ... )>
1414
1415Creates a new round robin database (RRD). A RRD consists of one or more
1416data sources and one or more archives:
1417
1418    $rrd->create(
1419         step        => 60,
1420         data_source => { name      => "mydatasource",
1421                          type      => "GAUGE" },
1422         archive     => { rows      => 5 });
1423
1424This defines a RRD database with a step rate of 60 seconds in between
1425primary data points. Additionally, the RRD start time can be specified
1426by specifying a C<start> parameter.
1427
1428It also sets up one data source named C<my_data_source>
1429of type C<GAUGE>, telling I<rrdtool> to use values of data samples
1430as-is, without additional trickery.
1431
1432And it creates a single archive with a 1:1 mapping between primary data
1433points and archive points, with a capacity to hold five data points.
1434
1435The RRD's C<step> parameter is optional, and will be set to 300 seconds
1436by I<rrdtool> by default.
1437
1438In addition to the mandatory settings for C<name> and C<type>,
1439C<data_source> parameter takes the following optional parameters:
1440C<min> (minimum input, defaults to C<U>),
1441C<max> (maximum input, defaults to C<U>),
1442C<heartbeat> (defaults to twice the RRD's step rate).
1443
1444Archives expect at least one parameter, C<rows> indicating the number
1445of data points the archive is configured to hold. If nothing else is
1446set, I<rrdtool> will store primary data points 1:1 in the archive.
1447
1448If you want
1449to combine several primary data points into one archive point, specify
1450values for
1451C<cpoints> (the number of points to combine) and C<cfunc>
1452(the consolidation function) explicitly:
1453
1454    $rrd->create(
1455         step        => 60,
1456         data_source => { name      => "mydatasource",
1457                          type      => "GAUGE" },
1458         archive     => { rows      => 5,
1459                          cpoints   => 10,
1460                          cfunc     => 'AVERAGE',
1461                        });
1462
1463This will collect 10 data points to form one archive point, using
1464the calculated average, as indicated by the parameter C<cfunc>
1465(Consolidation Function, CF). Other options for C<cfunc> are
1466C<MIN>, C<MAX>, and C<LAST>.
1467
1468If you're defining multiple data sources or multiple archives, just
1469provide them in this manner:
1470
1471       # Define the RRD
1472    my $rc = $rrd->create(
1473        step        => 60,
1474        data_source => { name      => 'load1',
1475                         type      => 'GAUGE',
1476                       },
1477        data_source => { name      => 'load2',
1478                         type      => 'GAUGE',
1479                       },
1480        archive     => { rows      => 5,
1481                         cpoints   => 10,
1482                         cfunc     => 'AVERAGE',
1483                        },
1484        archive     => { rows      => 5,
1485                         cpoints   => 10,
1486                         cfunc     => 'MAX',
1487                        },
1488    );
1489
1490=item I<$rrd-E<gt>update( ... ) >
1491
1492Update the round robin database with a new data sample,
1493consisting of a value and an optional time stamp.
1494If called with a single parameter, like in
1495
1496    $rrd->update($value);
1497
1498then the current timestamp and the defined C<$value> will be used.
1499If C<update> is called with a named parameter list like in
1500
1501    $rrd->update(time => $time, value => $value);
1502
1503then the given timestamp C<$time> is used along with the given value
1504C<$value>.
1505
1506When updating multiple data sources, use the C<values> parameter
1507(instead of C<value>) and pass an arrayref:
1508
1509    $rrd->update(time => $time, values => [$val1, $val2, ...]);
1510
1511This way, I<rrdtool> expects you to pass in the data values in
1512exactly the same order as the data sources were defined in the
1513C<create> method. If that's not the case,
1514then the C<values> parameter also accepts a hashref, mapping data source
1515names to values:
1516
1517    $rrd->update(time => $time,
1518                 values => { $dsname1 => $val1,
1519                             $dsname2 => $val2, ...});
1520
1521C<RRDTool::OO> will transform this automagically
1522into C<RRDTool's> I<template> syntax.
1523
1524=item I<$rrd-E<gt>updatev( ... )>
1525
1526This is identical to C<update>, but uses rrdtool's updatev function internally.
1527The only difference is when using the C<print_results> method described
1528below, which then contains additional information.
1529
1530=item I<$rrd-E<gt>fetch_start( ... )>
1531
1532Initializes the iterator to fetch data from the RRD. This works nicely without
1533any parameters if
1534your archives are using a single consolidation function (e.g. C<MAX>).
1535If there's several archives in the RRD using different consolidation
1536functions, you have to specify which one you want:
1537
1538    $rrd->fetch_start(cfunc => "MAX");
1539
1540Other options for C<cfunc> are C<MIN>, C<AVERAGE>, and C<LAST>.
1541
1542C<fetch_start> features a number of optional parameters:
1543C<start>, C<end> and C<resolution>.
1544
1545If the C<start>
1546time parameter is omitted, the fetch starts 24 hours before the end of the
1547archive. Also, an C<end> time can be specified:
1548
1549    $rrd->fetch_start(start => time()-10*60,
1550                      end   => time());
1551
1552The third optional parameter,
1553C<resolution> defaults to the highest resolution available and can
1554be set to a value in seconds, specifying the time interval between
1555the data samples extracted from the RRD.
1556See the C<rrdtool fetch> manual page for details.
1557
1558Development note: The current implementation
1559fetches I<all> values from the RRA in one swoop
1560and caches them in memory. This might
1561change in the future, to cache only the last timestamp and keep fetching
1562from the RRD with every C<fetch_next()> call.
1563
1564=item I<$rrd-E<gt>fetch_skip_undef()>
1565
1566I<rrdtool> doesn't remember the time the first data sample went into the
1567archive. So if you run a I<rrdtool fetch> with a start time of 24 hours
1568ago and you've only submitted a couple of samples to the archive, you'll
1569see many C<undef> values.
1570
1571Starting from the current iterator position (or at the specified C<start>
1572time immediately after a C<fetch_start()>), C<fetch_skip_undef()>
1573will skip all C<undef> values in the RRA and
1574positions the iterator right before the first defined value.
1575If all values in the RRA are undefined, the
1576a following C<$rrd-E<gt>fetch_next()> will return C<undef>.
1577
1578=item I<($time, $value, ...) = $rrd-E<gt>fetch_next()>
1579
1580Gets the next row from the RRD iterator, initialized by a previous call
1581to C<$rrd-E<gt>fetch_start()>. Returns the time of the archive point
1582along with all values as a list.
1583
1584Note that there might be more than one value coming back from C<fetch_next>
1585if the RRA defines more than one datasource):
1586
1587    I<($time, @values_of_all_ds) = $rrd-E<gt>fetch_next()>
1588
1589It is not possible to fetch only a specific datasource, as rrdtool
1590doesn't provide this.
1591
1592=item I<($time, $value, ...) = $rrd-E<gt>fetch_next()>
1593
1594=item I<$rrd-E<gt>graph( ... )>
1595
1596If there's only one data source in the RRD, drawing a nice graph in
1597an image file on disk is as easy as
1598
1599    $rrd->graph(
1600      image          => $image_file_name,
1601      vertical_label => 'My Salary',
1602      draw           => { thickness => 2,
1603                          color     => 'FF0000',
1604                          legend    => 'Salary over Time',
1605                        },
1606    );
1607
1608This will assume a start time of 24 hours before now and an
1609end time of now. Specify C<start> and C<end> explicitly to
1610be clear:
1611
1612    $rrd->graph(
1613      image          => $image_file_name,
1614      vertical_label => 'My Salary',
1615      start          => time() - 24*3600,
1616      end            => time(),
1617      draw           => { thickness => 2,
1618                          color     => 'FF0000',
1619                          legend    => 'Salary over Time',
1620                        },
1621    );
1622
1623As always, C<RRDTool::OO> will pick reasonable defaults for parameters
1624not specified. The values for data source and consolidation function
1625default to the first values it finds in the RRD.
1626If there are multiple datasources in the RRD or multiple archives
1627with different values for C<cfunc>, just specify explicitly which
1628one to draw:
1629
1630    $rrd->graph(
1631      image          => $image_file_name,
1632      vertical_label => 'My Salary',
1633      draw           => {
1634        thickness => 2,
1635        color     => 'FF0000',
1636        dsname    => "load",
1637        cfunc     => 'MAX'},
1638    );
1639
1640If C<draw> doesn't define a C<type>, it defaults to C<"line">. If
1641you don't want to define a type (because the graph shouldn't be drawn),
1642use C<type =E<gt> "hidden">. Other
1643values are C<"area"> for solid colored areas. The C<"stack"> type
1644(for graphical values stacked on top of each other)
1645has been deprecated sind rrdtool-1.2, but RRDTool::OO still supports it
1646by transforming it into an 'area' type with a 'stack' option.
1647
1648And you can certainly have more than one graph in the picture:
1649
1650    $rrd->graph(
1651      image          => $image_file_name,
1652      vertical_label => 'My Salary',
1653      draw           => {
1654        type      => 'area',
1655        color     => 'FF0000', # red area
1656        dsname    => "load",
1657        cfunc     => 'MAX'},
1658      draw        => {
1659        type      => 'area',
1660        stack     => 1,
1661        color     => '00FF00', # a green area stacked on top of the red one
1662        dsname    => "load",
1663        cfunc     => 'AVERAGE'},
1664    );
1665
1666Graphs may assemble data from different RRD files. Just specify
1667which file you want to draw the data from, using C<draw>:
1668
1669    $rrd->graph(
1670      image          => $image_file_name,
1671      vertical_label => 'Network Traffic',
1672      draw           => {
1673        file      => "file1.rrd",
1674        legend    => "First Source",
1675      },
1676      draw        => {
1677        file      => "file2.rrd",
1678        type      => 'area',
1679        stack     => 1,
1680        color     => '00FF00', # a green area stacked on top of the red one
1681        dsname    => "load",
1682        legend    => "Second Source",
1683        cfunc     => 'AVERAGE'
1684      },
1685    );
1686
1687If a C<file> parameter is specified per C<draw>, the defaults for C<dsname>
1688and C<cfunc> are fetched from this file, not from the file that's attached
1689to the C<RRDTool::OO> object C<$rrd> used.
1690
1691Graphs may also consist of algebraic calculations of previously defined
1692graphs. In this case, graphs derived from real data sources need to be named,
1693so that subsequent C<cdef> definitions can refer to them and calculate
1694new graphs, based on the previously defined graph:
1695
1696    $rrd->graph(
1697      image          => $image_file_name,
1698      vertical_label => 'Network Traffic',
1699      draw           => {
1700        type      => 'line',
1701        color     => 'FF0000', # red line
1702        dsname    => 'load',
1703        name      => 'firstgraph',
1704        legend    => 'Unmodified Load',
1705      },
1706      draw        => {
1707        type      => 'line',
1708        color     => '00FF00', # green line
1709        cdef      => "firstgraph,2,*",
1710        legend    => 'Load Doubled Up',
1711      },
1712    );
1713
1714Note that the second C<draw> doesn't refer to a datasource C<dsname>
1715(nor does it fall back to the default data source), but
1716defines a C<cdef>, performing calculations on a previously defined
1717draw named C<firstgraph>. The calculation is specified using
1718RRDTool's reverse polish notation, where instructions are separated by commas
1719(C<"firstgraph,2,*"> simply multiplies C<firstgraph>'s values by 2).
1720
1721On a global level, in addition to the C<vertical_label> parameter shown
1722in the examples above, C<graph> offers a plethora of parameters:
1723
1724C<vertical_label>,
1725C<title>,
1726C<start>,
1727C<end>,
1728C<x_grid>,
1729C<y_grid>,
1730C<alt_y_grid>,
1731C<no_minor>,
1732C<alt_y_mrtg>,
1733C<alt_autoscale>,
1734C<alt_autoscale_max>,
1735C<base>,
1736C<units_exponent>,
1737C<units_length>,
1738C<width>,
1739C<height>,
1740C<interlaced>,
1741C<imginfo>,
1742C<imgformat>,
1743C<overlay>,
1744C<unit>,
1745C<lazy>,
1746C<rigid>,
1747C<lower_limit>,
1748C<upper_limit>,
1749C<logarithmic>,
1750C<color>,
1751C<no_legend>,
1752C<only_graph>,
1753C<force_rules_legend>,
1754C<title>,
1755C<step>.
1756
1757Some options (e.g. C<alt_y_grid>) don't expect values, they need to
1758be specified like
1759
1760    alt_y_grid => undef
1761
1762in order to be passed properly to RRDTool.
1763
1764The C<color> option expects a reference to a hash with various settings
1765for the different graph areas:
1766C<back> (background),
1767C<canvas>,
1768C<shadea> (left/top border),
1769C<shadeb> (right/bottom border),
1770C<grid>, C<mgrid> major grid,
1771C<font>,
1772C<frame> and C<arrow>:
1773
1774    $rrd->graph(
1775      ...
1776      color          => { back   => '#0e0e0e',
1777                          arrow  => '#ff0000',
1778                          canvas => '#eebbbb',
1779                        },
1780      ...
1781    );
1782
1783Fonts for various graph elements may be specified in C<font> blocks,
1784which must either name a TrueType font file or a PDF/Postscript font name.
1785You may optionally specify a size and element name (defaults to DEFAULT,
1786which to RRD means "use this font for everything).  Example:
1787
1788    font  => {
1789        name => "/usr/openwin/lib/X11/fonts/TrueType/GillSans.ttf",
1790        size => 16,
1791        element => "title"
1792    }
1793
1794Please check the RRDTool documentation for a detailed description
1795on what each option is used for:
1796
1797    http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/rrdgraph.html
1798
1799Sometimes it's useful to print max, min or average values of
1800a given graph at the bottom of the chart or to STDOUT. That's what
1801C<gprint> and C<print> options are for. They are printing variables
1802which are defined as C<vdef>s somewhere else:
1803
1804    $rrd->graph(
1805      image          => $image_file_name,
1806          # Real graph
1807      draw           => {
1808        name      => "first_draw",
1809        dsname    => "load",
1810        cfunc     => 'MAX'
1811      },
1812
1813        # vdef for calculating average of real graph
1814      draw           => {
1815        type      => "hidden",
1816        name      => "average_of_first_draw",
1817        vdef      => "first_draw,AVERAGE"
1818      },
1819
1820      gprint         => {
1821        draw      => 'average_of_first_draw',
1822        format    => 'Average=%lf',
1823      },
1824    );
1825
1826The C<vdef> performs a calculation, specified in RPN notation, on
1827a real graph, which it refers to. It uses a hidden graph for this.
1828
1829The C<gprint> option then refers to the C<vdef> virtual graph and prints
1830"Average=x.xx" at the bottom of the graph, showing what the
1831average value of graph C<first_draw> is.
1832
1833To write comments to the graph (like gprints, but with no associated
1834RRD data source) use C<comment>, like this:
1835
1836    $rrd->graph(
1837      image          => $image_file_name,
1838      draw           => {
1839        name      => "first_draw",
1840        dsname    => "load",
1841        cfunc     => 'MAX'},
1842      comment        => "Remember, 83% of all statistics are made up",
1843    );
1844
1845Multiple comment lines can be specified in a single comment specification
1846like this:
1847
1848     comment => [ "All the king's horses and all the king's men\\n",
1849                  "couldn't put Humpty together again.\\n",
1850                ],
1851
1852Vertical rules (lines) may be placed into the graph by using a C<vrule>
1853block like so:
1854
1855       vrule => { time => time()-3600, }
1856
1857These can be useful for indicating when the most recent day on the graph
1858started, for example.
1859
1860vrules can have a color specification (they default to black) and also
1861an optional legend string specified:
1862
1863      vrule => { time => $first_thing_today,
1864                 color => "#0000ff",
1865                 legend => "When we crossed midnight"
1866               },
1867
1868hrules can have a color specification (they default to black) and also
1869an optional legend string specified:
1870
1871      hrule => { value => $numeric_value,
1872                 color => "#0000ff",
1873                 legend => "a static line at your value"
1874               },
1875
1876Horizontal rules can be added by using a C<line> block
1877like in
1878
1879    line => {
1880        value   => "fixed num value or draw name",
1881        color   => "#0000ff",
1882        legend  => "a blue horizontal line",
1883        width   => 120,
1884        stack   => 1,
1885    }
1886
1887If instead of a horizontal line, a rectangular area is supposed to
1888be added to the graph, use an C<area> block:
1889
1890    area => {
1891        value   => "fixed num value or draw name",
1892        color   => "#0000ff",
1893        legend  => "a blue horizontal line",
1894        stack   => 1,
1895    }
1896
1897The C<graph> method can also generate tickmarks (vertical lines)
1898for every defined value, using the C<tick> option:
1899
1900    tick => {
1901        draw    => "drawname",
1902        color   => "#0000ff",
1903        legend  => "a blue horizontal line",
1904        stack   => 1,
1905    }
1906
1907The graph may be shifted relative to the time axis:
1908
1909    shift => {
1910        draw    => "drawname",
1911        offset  => $offset,
1912    }
1913
1914=item I<$rrd-E<gt>graphv( ... )>
1915
1916This is identical to C<graph>, but uses rrdtool's graphv function internally.
1917The only difference is when using the C<print_results> method described below, which
1918then contains additional information.
1919Be aware that rrdtool 1.3 is required for C<graphv> to work.
1920
1921=item I<$rrd-E<gt>dump()>
1922
1923I<Available as of rrdtool 1.0.49>.
1924
1925Dumps the RRD in XML format to STDOUT. If you want to dump it into a file
1926instead, do this:
1927
1928    my $pid;
1929
1930    unless ($pid = open DUMP, "-|") {
1931      die "Can't fork: $!" unless defined $pid;
1932      $rrd->dump();
1933      exit 0;
1934    }
1935
1936    waitpid($pid, 0);
1937
1938    open OUT, ">out";
1939    print OUT $_ for <DUMP>;
1940    close OUT;
1941
1942=item I<my $hashref = $rrd-E<gt>xport(...)>
1943
1944Feed a perl structure with RRA data (Cf. rrdxport man page).
1945
1946    my $results = $rrd->xport(
1947        start => $start_time,
1948        end => $end_time ,
1949        step => $step,
1950        def => [{
1951            vname => "load1_vname",
1952            file => "foo",
1953            dsname => "load1",
1954            cfunc => "MAX",
1955        },
1956        {
1957            vname => "load2_vname",
1958            file => "foo",
1959            dsname => "load2",
1960            cfunc => "MIN",
1961        }],
1962
1963        cdef => [{
1964            vname => "load2_vname_multiply",
1965            rpn => "load2_vname,2,*",
1966        }],
1967
1968        xport => [{
1969            vname => "load1_vname",
1970            legend => "it_s_gonna_be_legend_",
1971        },
1972        {
1973            vname => "load2_vname",
1974            legend => "wait_for_it",
1975        },
1976        {
1977            vname => "load2_vname_multiply",
1978            legend => "___dary",
1979        }],
1980    );
1981
1982    my $data = $results->{data};
1983    my $metadata = $results->{meta};
1984
1985    print "### METADATA ###\n";
1986    print "StartTime: $metadata->{start}\n";
1987    print "EndTime: $metadata->{end}\n";
1988    print "Step: $metadata->{step}\n";
1989    print "Number of data columns: $metadata->{columns}\n";
1990    print "Number of data rows: $metadata->{rows}\n";
1991    print "Legend: ", join(", ", @{$metadata->{legend}}), "\n";
1992
1993    print "\n### DATA ###\n";
1994    foreach my $entry (@$data) {
1995        my $entry_timestamp = shift(@$entry);
1996        print "[$entry_timestamp] ", join(" ", @$entry), "\n";
1997    }
1998
1999=item I<my $hashref = $rrd-E<gt>info()>
2000
2001Grabs the RRD's meta data and returns it as a hashref, holding a
2002map of parameter names and their values.
2003
2004=item I<my $time = $rrd-E<gt>first()>
2005
2006Return the RRD's first update time.
2007
2008=item I<my $time = $rrd-E<gt>last()>
2009
2010Return the RRD's last update time.
2011
2012=item I<$rrd-E<gt>restore(xml =E<gt> "file.xml")>
2013
2014I<Available as of rrdtool 1.0.49>.
2015
2016Restore a RRD from a C<dump>. The C<xml> parameter specifies the name
2017of the XML file containing the dump. If the optional flag C<range_check>
2018is set to a true value, C<restore> will make sure the values in the
2019RRAs do not exceed the limits defined for the different datasources:
2020
2021    $rrd->restore(xml => "file.xml", range_check => 1);
2022
2023=item I<$rrd-E<gt>tune( ... )>
2024
2025Alter a RRD's data source configuration values:
2026
2027        # Set the heartbeat of the RRD's only datasource to 100
2028    $rrd->tune(heartbeat => 100);
2029
2030        # Set the minimum of DS 'load' to 1
2031    $rrd->tune(dsname => 'load', minimum => 1);
2032
2033        # Set the maximum of DS 'load' to 10
2034    $rrd->tune(dsname => 'load', maximum => 10);
2035
2036        # Set the type of DS 'load' to AVERAGE
2037    $rrd->tune(dsname => 'load', type => 'AVERAGE');
2038
2039        # Set the name of DS 'load' to 'load2'
2040    $rrd->tune(dsname => 'load', name => 'load2');
2041
2042=item I<$rrd-E<gt>error_message()>
2043
2044Return the message of the last error that occurred while interacting
2045with C<RRDTool::OO>.
2046
2047=back
2048
2049=head2 Aberrant behavior detection
2050
2051RRDTool supports aberrant behavior detection (ABD), which takes a data
2052source, stuffs its values into a special RRA, smoothes the data stream,
2053tries to predict future values and triggers an alert if actual values
2054are way off the predicted values.
2055
2056Using a fairly elaborate algorithm not only allows it to find out if
2057a data source produces a value that exceeds a certain fixed threshold.
2058The algorithm constantly adapts its parameters to the input data and
2059acts dynamically on slowly changing values.
2060
2061The C<alpha> parameter specifies the baseline and
2062lies between 0 and 1. Values close to 1 specify
2063that most recent values have the most weight on the prediction, whereas
2064values close to 0 indicate that past values carry higher weight.
2065
2066On top of that, ABD can deal with data input that displays continuously
2067rising values (slope). The C<beta> parameters, again between 0 and 1,
2068specifies whether past values or more recent values carry the most
2069weight.
2070
2071And, furthermore, it deals with seasonal cycles, so it won't freak out if
2072there's a daily peak at noon. The C<gamma> parameter indicates this, if
2073you don't specify it, it defaults to the value of C<alpha>.
2074
2075In the easiest case, an RRA with aberrant behavior detection can be
2076created like
2077
2078        # Create a round-robin database
2079    $rrd->create(
2080         step        => 1,  # one-second intervals
2081         data_source => { name      => "mydatasource",
2082                          type      => "GAUGE" },
2083         hwpredict   => { rows => 3600,
2084                        },
2085    );
2086
2087where C<alpha> and C<beta> default to 0.5, and the C<seasonal_period>
2088defaults to 1/5 of the rows number.
2089
2090C<rows> is the number of primary data points that are stored in the RRA
2091before a wrap-around happens. Note that with ABD enabled, RRDTool won't
2092consolidate the data from a data source before stuffing it into
2093the HWPREDICT RRAs, as the whole point of ABD is to smooth unfiltered
2094data and predict future values.
2095
2096A violation happens if a new measured value falls outside of the
2097prediction. If C<threshold> or more violations happen within
2098C<window_length>, an error is reported to the FAILURES RRA.
2099C<threshold> defaults to 7, C<window_length> to 9.
2100
2101A more elaborate RRD could be defined as
2102
2103        # Create a round-robin database
2104    $rrd->create(
2105         step        => 1,  # one-second intervals
2106         data_source => { name      => "mydatasource",
2107                          type      => "GAUGE" },
2108         hwpredict   => { rows          => 3600,
2109                          alpha         => 0.1,
2110                          beta          => 0.1,
2111                          gamma         => 0.1,
2112                          threshold     => 7,
2113                          window_length => 9,
2114                        },
2115    );
2116
2117If you want to peek under the hood (not that you need to, just
2118for your entertainment), with the specification above, RRDTool::OO will
2119create the following five RRAs according to the RRDtool
2120specification and fill in these values:
2121
2122    * RRA:HWPREDICT:rows:alpha:beta:seasonal_period:rra-num
2123    * RRA:SEASONAL:seasonal period:gamma:rra-num
2124    * RRA:DEVSEASONAL:seasonal period:gamma:rra-num
2125    * RRA:DEVPREDICT:rows:rra-num
2126    * RRA:FAILURES:rows:threshold:window_length:rra-num
2127
2128The C<rra-num> argument is an internal index referencing other
2129RRAs (for example, HWPREDICT references SEASONAL), but this will
2130be taken care of automatically by RRDTool::OO with no user
2131interaction required whatsoever.
2132
2133=head2 Development Status
2134
2135The following methods are not yet implemented:
2136
2137C<rrdresize>, C<xport>, C<rrdcgi>.
2138
2139=head2 Print Output
2140
2141The C<graph> method can be configured to have RRDTool's C<graph>
2142function to print data. Calling rrdtool on the command line, this
2143data ends up on STDOUT, but calling something like
2144
2145    $rrd->graph(
2146      image          => "mygraph.png",
2147      start          => $start_time,
2148
2149      # ...
2150
2151      draw           => {
2152          type      => "hidden",
2153          name      => "in95precent",
2154          vdef      => "firstdraw,95,PERCENT"
2155      },
2156
2157      print         => {
2158          draw      => 'in95percent',
2159          format    => "95 Percent Result = %3.2lf",
2160        },
2161
2162      # ...
2163
2164captures the print data internally. To get access to a reference to the array
2165containing the different pieces of data written in this way, call
2166
2167    my $array_ref = $rrd->print_results();
2168
2169If no print output is available, the array referenced by C<$array_ref>
2170is empty.
2171
2172If the C<graphv> function is used instead of C<graph>, the return value of
2173print_results is a hashref containing the same information in the C<print> keys,
2174along with additional keys containing detailed information on the graph. See C<rrdtool>
2175documentation for more detail. Here is an example:
2176
2177    use Data::Dumper;
2178
2179    $rrd -> graphv (
2180      image          => "-",
2181      start          => $start_time,
2182
2183      # ...
2184
2185    my $hash_ref = $rrd->print_results();
2186
2187    print Dumper $hash_ref;
2188    $VAR1 = {
2189          'print[2]' => '1600.00',
2190          'value_min' => '200',
2191          'image_height' => 64,
2192          'graph_height' => 10,
2193          'print[1]' => '3010.18',
2194          'graph_end' => 1249391462,
2195          'print[3]' => '1600.00',
2196          'graph_left' => 51,
2197          'print[4]' => '2337.29',
2198          'print[0]' => '305.13',
2199          'value_max' => '10000',
2200          'graph_width' => 10,
2201          'image_width' => 91,
2202          'graph_top' => 22,
2203          'image' => '#PNG
2204                     [...lots of binary rubbish your terminal won't like...]
2205                     ',
2206          'graph_start' => 1217855462
2207        };
2208
2209In this case, the option (image => "-") has been used to create the hash key
2210with the same name, the value of which actually contains the BLOB of the image itself.
2211This is useful when image needs to be passed to other modules (e.g. Image::Magick),
2212instead of writing it to disk.
2213Be aware that rrdtool 1.3 is required for C<graphv> to work.
2214
2215=head2 Error Handling
2216
2217By default, C<RRDTool::OO>'s methods will throw fatal errors (as in:
2218they're calling C<die>) if the underlying C<RRDs::*> commands indicate
2219failure.
2220
2221This behaviour can be overridden by calling the constructor with
2222the C<raise_error> flag set to false:
2223
2224    my $rrd = RRDTool::OO->new(
2225        file        => "myrrdfile.rrd",
2226        raise_error => 0,
2227    );
2228
2229In this mode, RRDTool's methods will just pass back values returned
2230from the underlying C<RRDs> functions if an error happens (usually
22311 if successful and C<undef> if an error occurs).
2232
2233=head2 Debugging
2234
2235C<RRDTool::OO> is C<Log::Log4perl> enabled, so if you want to know
2236what's going on under the hood, just turn it on:
2237
2238    use Log::Log4perl qw(:easy);
2239
2240    Log::Log4perl->easy_init({
2241        level    => $DEBUG
2242    });
2243
2244If you're interested particularly in I<rrdtool> commands issued
2245by C<RRDTool::OO> while you're operating it, just enable the
2246category C<"rrdtool">:
2247
2248    Log::Log4perl->easy_init({
2249        level    => $INFO,
2250        category => 'rrdtool',
2251        layout   => '%m%n',
2252    });
2253
2254
2255This will display all C<rrdtool> commands that C<RRDTool::OO> submits
2256to the shared library. Let's turn it on for the code snippet in the
2257SYNOPSIS section of this manual page and watch the output:
2258
2259    rrdtool create myrrdfile.rrd --step 1 \
2260            DS:mydatasource:GAUGE:2:U:U RRA:MAX:0.5:1:5
2261    rrdtool update myrrdfile.rrd N:1
2262    rrdtool update myrrdfile.rrd N:2
2263    rrdtool update myrrdfile.rrd N:3
2264    rrdtool fetch myrrdfile.rrd MAX
2265
2266Often handy for cut-and-paste.
2267
2268=head2 Allow New rrdtool Parameters
2269
2270C<RRDTool::OO> tracks rrdtool's progress loosely, so it might happen
2271that at a given point in time, rrdtool introduces a new option that
2272C<RRDTool::OO> doesn't know about yet.
2273
2274This might lead to problems, since default, C<RRDTool::OO> has its
2275C<strict> mode enabled, rejecting all unknown options. This mode is
2276usually helpful, because it catches typos (like C<"verical_label">),
2277but if you want to use a new rrdtool option, it's in the way.
2278
2279To work around this problem until a new version of C<RRDTool::OO>
2280supports the new parameter, you can use
2281
2282    $rrd->option_add("graph", "frobnication_level");
2283
2284to add it to the optional parameter list of the C<graph> (or whatever)
2285rrd function. Note that some functions in C<RRDTool::OO> have
2286sub-methods, which you can specify with the dash notation.
2287The C<graph> method with its various "graph/draw", "graph/color",
2288"graph/font" are notable examples.
2289
2290And, as a band-aid, you can disable strict mode in these situation
2291by setting the C<strict> parameter to 0 in C<RRDTool::OO>'s
2292constructor call:
2293
2294    my $rrd = RRDTool::OO->new(
2295        strict => 0,
2296        file   => "myrrdfile.rrd",
2297    );
2298
2299Note that C<RRDTool::OO> follows the convention that parameters
2300names do not contain dashes, but underscores instead. So, you need
2301to say C<"vertical_label">, not C<"vertical-label">. The underlying
2302rrdtool layer, however, expects dashes, not underscores, which is why
2303C<RRDTool::OO> converts them automatically, e.g. transforming
2304C<"vertical_label"> to C<"--vertical-label"> before the
2305underlying rrdtool call happens.
2306
2307=head2 Dry Run Mode
2308
2309If you want to use C<RRDTool::OO> to create RRD commands without
2310executing them directly, thanks to Jacquelin Charbonnel, there's the
2311I<dry run> mode. Here's how it works:
2312
2313    my $rrd = RRDTool::OO->new(
2314        file => "myrrdfile.rrd",
2315        dry_run => 1
2316    );
2317
2318With I<dry_run> set to a true value, you can run commands like
2319
2320    $rrd->create(
2321          step        => 60,
2322          data_source => { name      => "mydatasource",
2323                           type      => "GAUGE" },
2324          archive     => { rows      => 5 });
2325
2326but since I<dry_mode> is on, they won't be handed through to the
2327rrdtool layer anymore. Instead, RRDTool::OO allows you to retrieve
2328a reference to the RRDs function it was about to call including its
2329arguments:
2330
2331    my ($subref, $args) = $rrd->get_exec_env();
2332
2333You can now examine or modify the subroutine reference C<$subref> or
2334the arguments in the array reference C<$args>. Later, simply call
2335
2336    $subref->(@$args);
2337
2338to execute the RRDs function with the modified argument list later.
2339In this case, @$args would contain the following items:
2340
2341    ("myrrdfile.rrd", "--step", "60",
2342     "DS:mydatasource:GAUGE:120:U:U", "RRA:MAX:0.5:1:5")
2343
2344If you're interested in the RRD function name to be executed, retrieve
2345the third parameter of C<get_exec_env>:
2346
2347    my ($subref, $args, $funcname) = $rrd->get_exec_env();
2348
2349=head1 INSTALLATION
2350
2351C<RRDTool::OO> requires a I<rrdtool> installation with the
2352C<RRDs> Perl module, that comes with the C<rrdtool> distribution.
2353
2354Download the tarball from
2355
2356    http://oss.oetiker.ch/rrdtool/pub/rrdtool.tar.gz
2357
2358and then unpack, compile and install:
2359
2360    tar zxfv rrdtool.tar.gz
2361    cd rrdtool-1.2.26
2362    ./configure --enable-perl-site-install --prefix=/usr \
2363                --disable-tcl --disable-rrdcgi
2364    make
2365    make install
2366
2367    cd bindings/perl-shared
2368    perl Makefile.PL
2369    ./configure
2370    make
2371    make test
2372    make install
2373
2374=head1 SEE ALSO
2375
2376=over 4
2377
2378=item *
2379
2380Tobi Oetiker's RRDTool homepage at
2381
2382    http://rrdtool.org
2383
2384especially the manual page at
2385
2386        http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/index.html
2387
2388=item *
2389
2390My articles on rrdtool in
2391"Linux Magazine" (UK) and
2392"Linux Magazin" (Germany):
2393
2394        (English)
2395    http://www.linux-magazine.com/issue/44/Perl_RDDtool.pdf
2396        (German)
2397    http://www.linux-magazin.de/Artikel/ausgabe/2004/06/perl/perl.html
2398
2399=back
2400
2401=head1 AUTHOR
2402
2403Mike Schilli, E<lt>m@perlmeister.comE<gt>
2404
2405=head1 COPYRIGHT AND LICENSE
2406
2407Copyright (C) 2004-2009 by Mike Schilli
2408
2409This library is free software; you can redistribute it and/or modify
2410it under the same terms as Perl itself, either Perl version 5.8.3 or,
2411at your option, any later version of Perl 5 you may have available.
2412
2413=cut
2414