1 # Copyright (C) 2002-2011 Stanislav Sinyagin
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
18 # Stanislav Sinyagin <ssinyagin@yahoo.com>
20 package Torrus
::Renderer
::RRDtool
;
24 use Torrus
::ConfigTree
;
33 # All our methods are imported by Torrus::Renderer;
40 'height' => '--height',
41 'imgformat' => '--imgformat',
45 ('PNG' => 'image/png',
46 'SVG' => 'image/svg+xml',
47 'EPS' => 'application/postscript',
48 'PDF' => 'application/pdf');
50 my @arg_arrays = qw(opts defs bg hwtick hrule hwline line fg);
56 my $config_tree = shift;
61 if( not $config_tree->isLeaf($token) )
63 Error
("Token $token is not a leaf");
67 my $obj = {'args' => {}, 'dname' => 'A'};
69 for my $arrayName ( @arg_arrays )
71 $obj->{'args'}{$arrayName} = [];
74 push( @
{$obj->{'args'}{'opts'}},
75 $self->rrd_make_opts( $config_tree, $token, $view,
76 \
%rrd_graph_opts, $obj ) );
78 push( @
{$obj->{'args'}{'opts'}},
79 $self->rrd_make_graph_opts( $config_tree, $token, $view ) );
81 my $dstype = $config_tree->getNodeParam($token, 'ds-type');
83 if( $dstype eq 'rrd-multigraph' )
85 $self->rrd_make_multigraph( $config_tree, $token, $view, $obj );
89 my $leaftype = $config_tree->getNodeParam($token, 'leaf-type');
91 # Handle DEFs and CDEFs
92 # At the moment, we call the DEF as 'A'. Could change in the future
93 if( $leaftype eq 'rrd-def' )
96 $self->rrd_make_def( $config_tree, $token, $obj->{'dname'} );
97 return(undef) unless defined($defstring);
99 push( @
{$obj->{'args'}{'defs'}}, $defstring );
101 if( $self->rrd_check_hw( $config_tree, $token, $view ) )
103 $self->rrd_make_holtwinters( $config_tree, $token,
107 elsif( $leaftype eq 'rrd-cdef' )
109 my $expr = $config_tree->getNodeParam($token, 'rpn-expr');
110 push( @
{$obj->{'args'}{'defs'}},
111 $self->rrd_make_cdef($config_tree, $token,
112 $obj->{'dname'}, $expr) );
116 Error
("Unsupported leaf-type: $leaftype");
120 $self->rrd_make_graphline( $config_tree, $token, $view, $obj );
123 return(undef) if $obj->{'error'};
125 $self->rrd_make_hrules( $config_tree, $token, $view, $obj );
126 if( not $Torrus::Renderer
::ignoreDecorations
)
128 $self->rrd_make_decorations( $config_tree, $token, $view, $obj );
135 for my $arrayName ( @arg_arrays )
137 push( @args, @
{$obj->{'args'}{$arrayName}} );
139 Debug
('RRDs::graph arguments: ' . join(' ', @args));
141 # localize the TZ enviromennt for the child process
143 my $tz = $self->{'options'}->{'variables'}->{'TZ'};
144 if( not defined($tz) )
149 local $ENV{'TZ'} = $tz;
150 &RRDs
::graph
( $outfile, @args );
156 my $path = $config_tree->path($token);
157 Error
("$path $view: Error during RRD graph: $ERR");
161 my $mimetype = $obj->{'mimetype'};
162 if( not defined($mimetype) )
164 $mimetype = 'image/png';
167 return( $config_tree->getParam($view, 'expires')+time(), $mimetype );
173 'start' => '--start',
182 my $config_tree = shift;
187 if( not $config_tree->isLeaf($token) )
189 Error
("Token $token is not a leaf");
197 push( @arg_opts, $self->rrd_make_opts( $config_tree, $token, $view,
198 \
%rrd_print_opts, ) );
200 my $dstype = $config_tree->getNodeParam($token, 'ds-type');
202 if( $dstype eq 'rrd-multigraph' )
204 Error
("View type rrprint is not supported ".
205 "for DS type rrd-multigraph");
209 my $leaftype = $config_tree->getNodeParam($token, 'leaf-type');
211 # Handle DEFs and CDEFs
212 # At the moment, we call the DEF as 'A'. Could change in the future
214 if( $leaftype eq 'rrd-def' )
216 my $defstring = $self->rrd_make_def( $config_tree, $token, $dname );
217 return(undef) unless defined($defstring);
218 push( @arg_defs, $defstring );
220 elsif( $leaftype eq 'rrd-cdef' )
222 my $expr = $config_tree->getNodeParam($token, 'rpn-expr');
224 $self->rrd_make_cdef($config_tree, $token, $dname, $expr));
228 Error
("Unsupported leaf-type: $leaftype");
232 for my $cf ( split(',', $config_tree->getParam($view, 'print-cf')) )
234 push( @arg_print, sprintf( 'PRINT:%s:%s:%%le', $dname, $cf ) );
239 my @args = ( @arg_opts, @arg_defs, @arg_print );
240 Debug
('RRDs::graph arguments: ' . join(' ', @args));
244 # localize the TZ enviromennt for the child process
246 my $tz = $self->{'options'}->{'variables'}->{'TZ'};
247 if( not defined($tz) )
252 local $ENV{'TZ'} = $tz;
253 ($printout, undef, undef) = RRDs
::graph
('/dev/null', @args);
259 my $path = $config_tree->path($token);
260 Error
("$path $view: Error during RRD graph: $ERR");
264 my $fh = IO
::File
->new($outfile, 'w');
265 if( not defined($fh) )
267 printf $OUT ("%s\n", join(':', @
{$printout}));
272 $fh->printf("%s\n", join(':', @
{$printout}));
276 return( $config_tree->getParam($view, 'expires')+time(), 'text/plain' );
281 sub rrd_make_multigraph
284 my $config_tree = shift;
290 split(',', $config_tree->getNodeParam($token, 'ds-names') );
292 # We need this to refer to some existing variable name
293 $obj->{'dname'} = $dsNames[0];
295 # Analyze the drawing order
297 for my $dname ( @dsNames )
299 my $order = $config_tree->getNodeParam($token, 'line-order-'.$dname);
300 $dsOrder{$dname} = defined( $order ) ?
$order : 100;
303 my $disable_legend = $config_tree->getParam($view, 'disable-legend');
305 (defined($disable_legend) and $disable_legend eq 'yes') ?
1:0;
307 # make DEFs and Line instructions
311 if( not $disable_legend )
313 $do_gprint = $self->rrd_if_gprint( $config_tree, $token );
316 $self->rrd_make_gprint_header( $config_tree, $token, $view, $obj );
320 for my $dname ( sort {$dsOrder{$a} <=> $dsOrder{$b}} @dsNames )
324 $config_tree->getNodeParam($token, 'ignore-views-'.$dname);
325 if( defined( $ignoreViews ) and
326 grep {$_ eq $view} split(',', $ignoreViews) )
331 my $gprint_this = $do_gprint;
335 $config_tree->getNodeParam($token, 'disable-gprint-'.$dname);
336 if( defined( $ds_nogprint ) and $ds_nogprint eq 'yes' )
344 if( $dograph or $gprint_this )
346 my $expr = $config_tree->getNodeParam($token, 'ds-expr-'.$dname);
348 $self->rrd_make_cdef($config_tree, $token, $dname, $expr);
349 if( not scalar(@cdefs) )
355 push( @
{$obj->{'args'}{'defs'}}, @cdefs );
358 $config_tree->getNodeParam($token, 'graph-legend-'.$dname);
359 if( defined( $legend ) )
361 $legend =~ s/:/\\:/g;
371 $self->rrd_make_gprint( $dname, $legend,
372 $config_tree, $token, $view, $obj );
375 push( @
{$obj->{'args'}{'line'}},
376 'COMMENT:' . $legend . '\l');
381 # For datasource that disables gprint, there's no reason
389 $self->mkline( $config_tree->getNodeParam
390 ($token, 'line-style-'.$dname) );
393 $self->mkcolor( $config_tree->getNodeParam
394 ($token, 'line-color-'.$dname) );
397 $config_tree->getNodeParam($token, 'line-alpha-'.$dname);
398 if( defined( $alpha ) )
400 $linecolor .= $alpha;
404 $config_tree->getNodeParam($token, 'line-stack-'.$dname);
405 if( defined( $stack ) and $stack eq 'yes' )
414 push( @
{$obj->{'args'}{'line'}},
415 sprintf( '%s:%s%s%s%s', $linestyle, $dname,
417 ($legend ne '') ?
':'.$legend.'\l' : '',
426 # Check if Holt-Winters stuff is needed
430 my $config_tree = shift;
435 my $nodeHW = $config_tree->getNodeParam($token, 'rrd-hwpredict');
436 if( defined($nodeHW) and $nodeHW eq 'enabled' )
438 my $viewHW = $config_tree->getParam($view, 'rrd-hwpredict');
439 my $varNoHW = $self->{'options'}->{'variables'}->{'NOHW'};
441 if( (not defined($viewHW) or $viewHW ne 'disabled') and
451 sub rrd_make_holtwinters
454 my $config_tree = shift;
459 my $dname = $obj->{'dname'};
461 my $defstring = $self->rrd_make_def( $config_tree, $token,
462 $dname . 'pred', 'HWPREDICT' );
463 return() unless defined($defstring);
464 push( @
{$obj->{'args'}{'defs'}}, $defstring );
466 $defstring = $self->rrd_make_def( $config_tree, $token,
467 $dname . 'dev', 'DEVPREDICT' );
468 return() unless defined($defstring);
469 push( @
{$obj->{'args'}{'defs'}}, $defstring );
471 # Upper boundary definition
472 push( @
{$obj->{'args'}{'defs'}},
473 sprintf( 'CDEF:%supper=%spred,%sdev,2,*,+',
474 $dname, $dname, $dname ) );
476 # Lower boundary definition
477 push( @
{$obj->{'args'}{'defs'}},
478 sprintf( 'CDEF:%slower=%spred,%sdev,2,*,-',
479 $dname, $dname, $dname ) );
481 # Failures definition
482 $defstring = $self->rrd_make_def( $config_tree, $token,
483 $dname . 'fail', 'FAILURES' );
484 return() unless defined($defstring);
485 push( @
{$obj->{'args'}{'defs'}}, $defstring );
487 # Generate H-W Boundary Lines
490 my $hw_bndr_style = $config_tree->getParam($view, 'hw-bndr-style');
491 $hw_bndr_style = 'LINE1' unless defined $hw_bndr_style;
492 $hw_bndr_style = $self->mkline( $hw_bndr_style );
494 my $hw_bndr_color = $config_tree->getParam($view, 'hw-bndr-color');
495 $hw_bndr_color = '#FF0000' unless defined $hw_bndr_color;
496 $hw_bndr_color = $self->mkcolor( $hw_bndr_color );
498 push( @
{$obj->{'args'}{'hwline'}},
499 sprintf( '%s:%supper%s:%s',
500 $hw_bndr_style, $dname, $hw_bndr_color,
501 $Torrus::Renderer
::hwGraphLegend ?
'Boundaries\n':'' ) );
502 push( @
{$obj->{'args'}{'hwline'}},
503 sprintf( '%s:%slower%s',
504 $hw_bndr_style, $dname, $hw_bndr_color ) );
508 my $hw_fail_color = $config_tree->getParam($view, 'hw-fail-color');
509 $hw_fail_color = '#FFFFA0' unless defined $hw_fail_color;
510 $hw_fail_color = $self->mkcolor( $hw_fail_color );
512 push( @
{$obj->{'args'}{'hwtick'}},
513 sprintf( 'TICK:%sfail%s:1.0:%s',
514 $dname, $hw_fail_color,
515 $Torrus::Renderer
::hwGraphLegend ?
'Failures':'') );
521 sub rrd_make_graphline
524 my $config_tree = shift;
531 my $disable_legend = $config_tree->getParam($view, 'disable-legend');
532 if( not defined($disable_legend) or $disable_legend ne 'yes' )
534 $legend = $config_tree->getNodeParam($token, 'graph-legend');
535 if( defined( $legend ) )
537 $legend =~ s/:/\\:/g;
541 if( not defined( $legend ) )
546 my $styleval = $config_tree->getNodeParam($token, 'line-style');
547 if( not defined($styleval) )
549 $styleval = $config_tree->getParam($view, 'line-style');
552 my $linestyle = $self->mkline( $styleval );
554 my $colorval = $config_tree->getNodeParam($token, 'line-color');
555 if( not defined($colorval) )
557 $colorval = $config_tree->getParam($view, 'line-color');
560 my $linecolor = $self->mkcolor( $colorval );
562 if( $self->rrd_if_gprint( $config_tree, $token ) )
564 $self->rrd_make_gprint_header( $config_tree, $token, $view, $obj );
566 $self->rrd_make_gprint( $obj->{'dname'}, $legend,
567 $config_tree, $token, $view, $obj );
570 push( @
{$obj->{'args'}{'line'}},
571 sprintf( '%s:%s%s%s', $linestyle, $obj->{'dname'}, $linecolor,
572 ($legend ne '') ?
':'.$legend.'\l' : '' ) );
577 # Generate RRDtool arguments for HRULE's
582 my $config_tree = shift;
587 my $hrulesList = $config_tree->getParam($view, 'hrules');
588 if( defined( $hrulesList ) )
590 for my $hruleName ( split(',', $hrulesList ) )
592 # The presence of this parameter is checked by Validator
594 $config_tree->getParam( $view, 'hrule-value-'.$hruleName );
595 my $value = $config_tree->getNodeParam( $token, $valueParam );
597 if( defined( $value ) )
600 $config_tree->getParam($view, 'hrule-color-'.$hruleName);
601 $color = $self->mkcolor( $color );
604 $config_tree->getNodeParam($token,
605 'hrule-legend-'.$hruleName);
607 my $arg = sprintf( 'HRULE:%e%s', $value, $color );
608 if( defined( $legend ) and $legend =~ /\S/ )
610 $arg .= ':' . $legend . '\l';
612 push( @
{$obj->{'args'}{'hrule'}}, $arg );
620 sub rrd_make_decorations
623 my $config_tree = shift;
628 my $decorList = $config_tree->getParam($view, 'decorations');
630 $config_tree->getNodeParam($token, 'graph-ignore-decorations');
631 if( defined( $decorList ) and
632 (not defined($ignore_decor) or $ignore_decor ne 'yes') )
635 for my $decorName ( split(',', $decorList ) )
638 $config_tree->getParam($view, 'dec-order-' . $decorName);
639 $decor->{$order} = {'def' => [], 'line' => ''};
642 $self->mkline( $config_tree->
643 getParam
($view, 'dec-style-' . $decorName) );
645 $self->mkcolor( $config_tree->
646 getParam
($view, 'dec-color-' . $decorName) );
647 my $expr = $config_tree->
648 getParam
($view, 'dec-expr-' . $decorName);
651 $self->rrd_make_cdef( $config_tree, $token, $decorName,
652 $obj->{'dname'} . ',POP,' . $expr );
655 push( @
{$decor->{$order}{'def'}}, @cdefs );
656 $decor->{$order}{'line'} =
657 sprintf( '%s:%s%s', $style, $decorName, $color );
665 for my $order ( sort {$a<=>$b} keys %{$decor} )
667 my $array = $order < 0 ?
'bg':'fg';
669 push( @
{$obj->{'args'}{'defs'}}, @
{$decor->{$order}{'def'}} );
670 push( @
{$obj->{'args'}{$array}}, $decor->{$order}{'line'} );
676 # Takes the parameters from the view, and composes the list of
682 my $config_tree = shift;
689 for my $param ( keys %{$opthash} )
692 $self->{'options'}->{'variables'}->{'G' . $param};
694 if( not defined( $value ) )
696 $value = $config_tree->getParam( $view, $param );
699 if( defined( $value ) )
701 if( ( $param eq 'start' or $param eq 'end' ) and
702 defined( $self->{'options'}->{'variables'}->{'NOW'} ) )
704 my $now = $self->{'options'}->{'variables'}->{'NOW'};
705 if( index( $value , 'now' ) >= 0 )
707 $value =~ s/now/$now/;
709 elsif( $value =~ /^(\-|\+)/ )
711 $value = $now . $value;
714 elsif( $param eq 'imgformat' )
716 if( not defined($mime_type{$value}) )
718 Error
('Unsupported value for imgformat: ' . $value);
724 $obj->{'mimetype'} = $mime_type{$value};
728 push( @args, $opthash->{$param}, $value );
732 my $params = $config_tree->getParam($view, 'rrd-params');
733 if( defined( $params ) )
735 push( @args, split('\s+', $params) );
738 my $scalingbase = $config_tree->getNodeParam($token, 'rrd-scaling-base');
739 if( defined($scalingbase) and $scalingbase == 1024 )
741 push( @args, '--base', '1024' );
748 sub rrd_make_graph_opts
751 my $config_tree = shift;
757 my $graph_log = $config_tree->getNodeParam($token, 'graph-logarithmic');
758 if( defined($graph_log) and $graph_log eq 'yes' )
760 push( @args, '--logarithmic' );
764 $config_tree->getParam($view, 'disable-title');
765 if( not defined( $disable_title ) or $disable_title ne 'yes' )
767 my $title = $config_tree->getNodeParam($token, 'graph-title');
768 if( not defined($title) )
772 push( @args, '--title', $title );
776 $config_tree->getParam($view, 'disable-vertical-label');
777 if( not defined( $disable_vlabel ) or $disable_vlabel ne 'yes' )
780 $config_tree->getNodeParam($token, 'vertical-label');
781 if( defined( $vertical_label ) )
783 push( @args, '--vertical-label', $vertical_label );
787 my $ignore_limits = $config_tree->getParam($view, 'ignore-limits');
788 if( not defined($ignore_limits) or $ignore_limits ne 'yes' )
790 my $ignore_lower = $config_tree->getParam($view, 'ignore-lower-limit');
791 if( not defined($ignore_lower) or $ignore_lower ne 'yes' )
794 $config_tree->getNodeParam($token, 'graph-lower-limit');
795 if( defined($limit) )
797 push( @args, '--lower-limit', $limit );
801 my $ignore_upper = $config_tree->getParam($view, 'ignore-upper-limit');
802 if( not defined($ignore_upper) or $ignore_upper ne 'yes' )
805 $config_tree->getNodeParam($token, 'graph-upper-limit');
806 if( defined($limit) )
808 push( @args, '--upper-limit', $limit );
812 my $rigid_boundaries =
813 $config_tree->getNodeParam($token, 'graph-rigid-boundaries');
814 if( defined($rigid_boundaries) and $rigid_boundaries eq 'yes' )
816 push( @args, '--rigid' );
820 if( scalar( @Torrus::Renderer
::graphExtraArgs
) > 0 )
822 push( @args, @Torrus::Renderer
::graphExtraArgs
);
832 my $config_tree = shift;
837 my $datafile = $config_tree->getNodeParam($token, 'data-file');
838 my $dataddir = $config_tree->getNodeParam($token, 'data-dir');
839 my $rrdfile = $dataddir.'/'.$datafile;
840 if( not -r
$rrdfile )
842 my $path = $config_tree->path($token);
843 Error
("$path: No such file or directory: $rrdfile");
847 my $ds = $config_tree->getNodeParam($token, 'rrd-ds');
848 if( not defined $cf )
850 $cf = $config_tree->getNodeParam($token, 'rrd-cf');
852 return sprintf( 'DEF:%s=%s:%s:%s',
853 $dname, $rrdfile, $ds, $cf );
864 # Moved the validation part to Torrus::ConfigTree::Validator
868 my $config_tree = shift;
876 # We will name the DEFs as $dname.sprintf('%.2d', $ds_couter++);
879 my $rpn = Torrus
::RPN
->new();
881 # The callback for RPN translation
884 my ($noderef, $timeoffset) = @_;
887 if( $noderef =~ s/^(.+)\@// )
893 if( defined( $function ) and $cfNames{$function} )
899 my $leaf = ($noderef ne '') ?
900 $config_tree->getRelative($token, $noderef) : $token;
902 my $varname = $dname . sprintf('%.2d', $ds_couter++);
904 $self->rrd_make_def( $config_tree, $leaf, $varname, $cf );
905 if( not defined($defstring) )
911 push( @args, $defstring );
916 $expr = $rpn->translate( $expr, $callback );
918 push( @args, sprintf( 'CDEF:%s=%s', $dname, $expr ) );
927 my $config_tree = shift;
930 my $disable = $config_tree->getNodeParam($token, 'graph-disable-gprint');
931 if( defined( $disable ) and $disable eq 'yes' )
943 my $config_tree = shift;
950 my $gprintValues = $config_tree->getParam($view, 'gprint-values');
951 if( defined( $gprintValues ) )
953 for my $gprintVal ( split(',', $gprintValues ) )
956 $config_tree->getParam($view, 'gprint-format-' . $gprintVal);
957 push( @args, 'GPRINT:' . $vname . ':' . $format );
961 push( @
{$obj->{'args'}{'line'}}, @args );
966 sub rrd_make_gprint_header
969 my $config_tree = shift;
974 my $gprintValues = $config_tree->getParam($view, 'gprint-values');
975 if( defined( $gprintValues ) )
977 my $gprintHeader = $config_tree->getParam($view, 'gprint-header');
978 if( defined( $gprintHeader ) )
980 push( @
{$obj->{'args'}{'line'}},
981 'COMMENT:' . $gprintHeader . '\l' );
993 my $recursionLimit = 100;
995 while( $color =~ /^\#\#(\S+)$/ )
997 if( $recursionLimit-- <= 0 )
999 Error
('Color recursion is too deep');
1005 $color = $Torrus::Renderer
::graphStyles
{$colorName}{'color'};
1006 if( not defined( $color ) )
1008 Error
('No color is defined for ' . $colorName);
1021 if( $line =~ m/^\#\#(\S+)$/ )
1024 $line = $Torrus::Renderer
::graphStyles
{$lineName}{'line'};
1025 if( not defined( $line ) )
1027 Error
('No line style is defined for ' . $lineName);
1042 # indent-tabs-mode: nil
1043 # perl-indent-level: 4