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