%PDF- %PDF-
| Direktori : /usr/share/perl5/vendor_perl/Munin/Master/ |
| Current File : //usr/share/perl5/vendor_perl/Munin/Master/GraphOld.pm |
package Munin::Master::GraphOld;
# -*- cperl -*-
=encoding utf-8
=begin comment
This is Munin::Master::GraphOld, a package shell to make munin-graph
modular (so it can loaded persistently in munin-cgi-graph for example)
without making it object oriented yet. The non "old" module will
feature propper object orientation like munin-update and will have to
wait until later.
Copyright (C) 2002-2010 Jimmy Olsen, Audun Ytterdal, Kjell Magne
Øierud, Nicolai Langfeldt, Linpro AS, Redpill Linpro AS and others.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2 dated June,
1991.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
$Id$
=end comment
=cut
use warnings;
use strict;
use Exporter;
our (@ISA, @EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw(graph_startup graph_check_cron graph_main graph_config);
use IO::Socket;
use IO::Handle;
use RRDs;
use POSIX qw(strftime);
use Digest::MD5;
use Getopt::Long;
use Time::HiRes;
use Text::ParseWords;
# For UTF-8 handling (plugins are assumed to use Latin 1)
if ($RRDs::VERSION >= 1.3) {
use Encode;
use Encode::Guess;
Encode->import;
Encode::Guess->import;
}
use Munin::Master::Logger;
use Munin::Master::Utils;
use Munin::Common::Defaults;
use Log::Log4perl qw( :easy );
# RRDtool 1.2 requires \\: in comments
my $RRDkludge = $RRDs::VERSION < 1.2 ? '' : '\\';
# And RRDtool 1.2.* draws lines with crayons so we hack
# the LINE* options a bit.
my $LINEkluge = 0;
if ($RRDs::VERSION >= 1.2 and $RRDs::VERSION < 1.3) {
# Only kluge the line widths in RRD 1.2*
$LINEkluge = 1;
}
# RRD 1.3 has a "ADDNAN" operator which evaluates n + NaN = n instead of = NaN.
my $AddNAN = '+';
if ($RRDs::VERSION >= 1.3) {
$AddNAN = 'ADDNAN';
}
# the ":dashes" syntax for LINEs is supported since rrdtool 1.5.3
my $RRDLineThresholdAttribute = ($RRDs::VERSION < 1.50003) ? '' : ':dashes';
# Force drawing of "graph no".
my $force_graphing = 0;
my $force_lazy = 1;
my $do_usage = 0;
my $do_version = 0;
my $cron = 0;
my $list_images = 0;
my $output_file = undef;
my $log_file = undef;
my $skip_locking = 0;
my $skip_stats = 0;
my $stdout = 0;
my $force_run_as_root = 0;
my $conffile = $Munin::Common::Defaults::MUNIN_CONFDIR . "/munin.conf";
my $libdir = $Munin::Common::Defaults::MUNIN_LIBDIR;
# Note: Nothing by default is more convenient and elliminates code while
# for cgi graphing - but it breaks how munin-graph expected stuff to work.
# I think.
my %draw = (
'day' => 0,
'week' => 0,
'month' => 0,
'year' => 0,
'sumyear' => 0,
'sumweek' => 0,
'pinpoint' => 0,
);
my %init_draw = %draw;
my $pinpoint = {};
my ($size_x, $size_y, $full_size_mode, $only_graph);
my ($lower_limit, $upper_limit);
my %PALETTE; # Hash of available palettes
my @COLOUR; # Array of actuall colours to use
{
no warnings;
$PALETTE{'old'} = [ # This is the old munin palette. It lacks contrast.
qw(22ff22 0022ff ff0000 00aaaa ff00ff
ffa500 cc0000 0000cc 0080C0 8080C0 FF0080
800080 688e23 408080 808000 000000 00FF00
0080FF FF8000 800000 FB31FB
)];
$PALETTE{'default'} = [ # New default palette.Better contrast,more colours
#Greens Blues Oranges Dk yel Dk blu Purple lime Reds Gray
qw(00CC00 0066B3 FF8000 FFCC00 330099 990099 CCFF00 FF0000 808080
008F00 00487D B35A00 B38F00 6B006B 8FB300 B30000 BEBEBE
80FF80 80C9FF FFC080 FFE680 AA80FF EE00CC FF8080
666600 FFBFFF 00FFCC CC6699 999900
)]; # Line variations: Pure, earthy, dark pastel, misc colours
}
my $range_colour = "22ff22";
my $single_colour = "00aa00";
# Use 400 x RRA step, in order to have 1px per RRA sample.
my %times = (
"day" => "-2000m", # (i.e. -33h20m)
"week" => "-12000m", # (i.e. -8d13h20m)
"month" => "-48000m", # (i.e. -33d8h)
"year" => "-400d",
"pinpoint" => "dummy",
);
my %resolutions = (
"day" => "300",
"week" => "1500",
"month" => "7200",
"year" => "86400"
);
my %sumtimes = ( # time => [ label, seconds-in-period ]
"week" => ["hour", 12],
"year" => ["day", 288]);
# Limit graphing to certain hosts and/or services
my @limit_hosts = ();
my @limit_services = ();
my $only_fqn = '';
my $watermark = "Munin " . $Munin::Common::Defaults::MUNIN_VERSION;
# RRD param for RRDCACHED_ADDRESS
my @rrdcached_params;
my $running = 0;
my $max_running = 6;
my $do_fork = 1;
# "global" Configuration hash
my $config = undef;
# stats file handle
my $STATS;
my @init_limit_hosts = @limit_hosts;
my @init_limit_services = @limit_services;
sub process_pinpoint {
my ($pinpoint, $arg_name, $arg_value) = @_;
# XXX - Special hack^h^h^h^h treatment for --pinpoint
if ($arg_value && $arg_value =~ m/^(\d+),(\d+)$/ ) {
# "pinpoint" replaces all the other timing options
$draw{'day'}=0;
$draw{'week'}=0;
$draw{'month'}=0;
$draw{'year'}=0;
$draw{'sumweek'}=0;
$draw{'sumyear'}=0;
$draw{'pinpoint'}=1;
$$pinpoint->{'start'} = $1; # preparsed values
$$pinpoint->{'end'} = $2;
}
}
sub process_fqn {
my ($fqn, $arg) = @_;
# Reset what to draw whenever we specify a new fqn
$draw{'day'} = $draw{'week'} = $draw{'month'} = $draw{'year'} =
$draw{'sumweek'} = $draw{'sumyear'} = $draw{'pinpoint'} = 0;
return $arg;
}
sub graph_startup {
# Parse options and set up. Stuff that is usually only needed once.
#
# Do once pr. run, pr possebly once pr. graph in the case of
# munin-cgi-graph
# Localise the stuff, overwise it will be stacked up with CGI
%draw = %init_draw;
@limit_hosts = @init_limit_hosts;
@limit_services = @init_limit_services;
$pinpoint = undef;
my $pinpointopt = undef;
$force_graphing = 0;
$force_lazy = 1;
$do_usage = 0;
$do_version = 0;
$cron = 0;
$list_images = 0;
$output_file = undef;
$log_file = undef;
$skip_locking = 0;
$skip_stats = 0;
$stdout = 0;
$size_x = undef;
$size_y = undef;
$full_size_mode = undef;
$only_graph = undef;
$lower_limit = undef;
$upper_limit = undef;
# Get options
my ($args) = @_;
local @ARGV = @{$args};
# NOTE! Some of these options are available in graph_main too
# if you make changes here, make them there too.
my $debug;
&print_usage_and_exit
unless GetOptions (
"force!" => \$force_graphing,
"lazy!" => \$force_lazy,
"host=s" => \@limit_hosts,
"service=s" => \@limit_services,
"only-fqn=s" => sub{ $only_fqn = process_fqn(@_); },
"config=s" => \$conffile,
"stdout!" => \$stdout,
"force-run-as-root!" => \$force_run_as_root,
"day!" => \$draw{'day'},
"week!" => \$draw{'week'},
"month!" => \$draw{'month'},
"year!" => \$draw{'year'},
"pinpoint=s" => sub{ process_pinpoint(\$pinpoint,@_); },
"sumweek!" => \$draw{'sumweek'},
"sumyear!" => \$draw{'sumyear'},
"size_x=i" => \$size_x,
"size_y=i" => \$size_y,
"full_size_mode!"=> \$full_size_mode,
"only_graph!"=> \$only_graph,
"upper_limit=s" => \$upper_limit,
"lower_limit=s" => \$lower_limit,
"list-images!" => \$list_images,
"o|output-file=s" => \$output_file,
"l|log-file=s" => \$log_file,
"skip-locking!" => \$skip_locking,
"skip-stats!" => \$skip_stats,
"version!" => \$do_version,
"cron!" => \$cron,
"fork!" => \$do_fork,
"n=n" => \$max_running,
"help" => \$do_usage,
"debug!" => \$debug,
);
if ($do_version) {
print_version_and_exit();
}
if ($do_usage) {
print_usage_and_exit();
}
exit_if_run_by_super_user() unless $force_run_as_root;
# Only read $config once (thx Jani M.)
#
# FIXME - the loaded $config is stale within 5 minutes.
# we either need to die or restart ourselves when this
# happens.
if (!defined($config)) {
munin_readconfig_base($conffile);
# XXX: check if it needs datafile at that point
$config = munin_readconfig_part('datafile', 0);
}
$config->{debug} = $debug;
my $palette = &munin_get($config, "palette", "default");
$max_running = &munin_get($config, "max_graph_jobs", $max_running);
if ($config->{"rrdcached_socket"}) {
if ($RRDs::VERSION >= 1.3){
# Using the RRDCACHED_ADDRESS environnement variable, as
# it is way less intrusive than the command line args.
$ENV{RRDCACHED_ADDRESS} = $config->{"rrdcached_socket"};
} else {
ERROR "[ERROR] RRDCached feature ignored: RRD version must be at least 1.3. Version found: " . $RRDs::VERSION;
}
}
if ($max_running == 0) {
$do_fork = 0;
}
if (defined($PALETTE{$palette})) {
@COLOUR = @{$PALETTE{$palette}};
}
else {
die "Unknown palette named by 'palette' keyword: $palette\n";
}
return $config;
}
sub graph_check_cron {
# Are we running from cron and do we have matching graph_strategy
if (&munin_get($config, "graph_strategy", "cron") ne "cron" and $cron) {
# Strategy mismatch: We're run from cron, but munin.conf says
# we use dynamic graph generation
return 0;
}
# Strategy match:
return 1;
}
sub graph_main {
my ($args) = @_;
local @ARGV = @{$args};
# The loaded $config is stale within 5 minutes.
# So, we need to reread it when this happens.
$config = munin_readconfig_part('datafile');
# Reset an eventual custom size
$size_x = undef;
$size_y = undef;
$full_size_mode = undef;
$only_graph = undef;
$lower_limit = undef;
$upper_limit = undef;
$pinpoint = undef;
# XXX [DEBUG]
my $debug = undef;
GetOptions (
"host=s" => \@limit_hosts,
"only-fqn=s" => sub { $only_fqn = process_fqn(@_); },
"day!" => \$draw{'day'},
"week!" => \$draw{'week'},
"month!" => \$draw{'month'},
"year!" => \$draw{'year'},
"pinpoint=s" => sub{ process_pinpoint(\$pinpoint,@_); },
"sumweek!" => \$draw{'sumweek'},
"sumyear!" => \$draw{'sumyear'},
"o|output-file=s" => \$output_file,
# XXX [DEBUG]
"debug!" => \$debug,
"size_x=i" => \$size_x,
"size_y=i" => \$size_y,
"full_size_mode!"=> \$full_size_mode,
"only_graph!" => \$only_graph,
"upper_limit=s" => \$upper_limit,
"lower_limit=s" => \$lower_limit,
);
# XXX [DEBUG]
logger_debug() if $debug;
my $graph_time = Time::HiRes::time;
munin_runlock("$config->{rundir}/munin-graph.lock") unless $skip_locking;
unless ($skip_stats) {
open($STATS, '>', "$config->{dbdir}/munin-graph.stats.tmp")
or WARN "[WARNING] Unable to open $config->{dbdir}/munin-graph.stats.tmp";
autoflush $STATS 1;
}
process_work(@limit_hosts);
$graph_time = sprintf("%.2f", (Time::HiRes::time - $graph_time));
rename(
"$config->{dbdir}/munin-graph.stats.tmp",
"$config->{dbdir}/munin-graph.stats"
);
close $STATS unless $skip_stats;
munin_removelock("$config->{rundir}/munin-graph.lock") unless $skip_locking;
$running = wait_for_remaining_children($running);
}
# --------------------------------------------------------------------------
sub get_title {
my $service = shift;
my $scale = shift;
my $scale_text;
if ($pinpoint) {
my $start_text = localtime($pinpoint->{"start"});
my $end_text = localtime($pinpoint->{"end"});
$scale_text = "from $start_text to $end_text";
} else {
$scale_text = "by " . $scale;
}
my $title = munin_get($service, "graph_title", $service);
# Substitute ${graph_period} in title
my $period = munin_get($service, "graph_period", "second");
$title =~ s/\$\{graph_period\}/$period/g;
return ("$title - $scale_text");
}
sub get_custom_graph_args {
my $service = shift;
my $result = [];
my $args = munin_get($service, "graph_args");
if (defined $args) {
my $result = [ grep /\S/, "ewords('\s+', 0, $args) ];
return $result;
}
else {
return;
}
}
# insert these arguments after all others
# needed for your own VDEF/CDEF/DEF combinations
sub get_custom_graph_args_after {
my $service = shift;
my $result = [];
my $args = munin_get($service, "graph_args_after");
if (defined $args) {
my $result = ["ewords('\s+', 0, $args)];
return $result;
}
else {
return;
}
}
# set a graph end point in the future
# needed for CDEF TREND and PREDICT
sub get_end_offset {
my $service = shift;
# get number of seconds in future
return munin_get($service, "graph_future", 0);
}
sub get_vlabel {
my $service = shift;
my $scale = munin_get($service, "graph_period", "second");
my $res = munin_get($service, "graph_vlabel",
munin_get($service, "graph_vtitle"));
if (defined $res) {
$res =~ s/\$\{graph_period\}/$scale/g;
}
return $res;
}
sub should_scale {
my $service = shift;
my $ret;
if (!defined($ret = munin_get_bool($service, "graph_scale"))) {
$ret = !munin_get_bool($service, "graph_noscale", 0);
}
return $ret;
}
sub get_header {
my $service = shift;
my $scale = shift;
my $sum = shift;
my $result = [];
my $tmp_field;
# Picture filename
push @$result, get_picture_filename($service, $scale, $sum || undef);
# Title
push @$result, ("--title", get_title($service, $scale));
# When to start the graph
if ($pinpoint) {
push @$result, "--start", $pinpoint->{start};
push @$result, "--end", $pinpoint->{end};
} else {
push @$result, "--start", $times{$scale};
}
# Custom graph args, vlabel and graph title
if (defined($tmp_field = get_custom_graph_args($service))) {
push(@$result, @{$tmp_field});
}
if (defined($tmp_field = get_vlabel($service))) {
push @$result, ("--vertical-label", $tmp_field);
}
push @$result, '--slope-mode' if $RRDs::VERSION >= 1.2;
push @$result, "--height", ($size_y || munin_get($service, "graph_height", "175"));
push @$result, "--width", ($size_x || munin_get($service, "graph_width", "400"));
push @$result, "--full-size-mode" if ($full_size_mode);
push @$result, "--only-graph" if ($only_graph);
push @$result,"--rigid" if (defined $lower_limit || defined $upper_limit);
push @$result, "--imgformat", "PNG";
push @$result, "--lazy" if ($force_lazy);
push(@$result, "--units-exponent", "0") if (!should_scale($service));
return $result;
}
sub get_sum_command {
my $field = shift;
return munin_get($field, "sum");
}
sub get_stack_command {
my $field = shift;
return munin_get($field, "stack");
}
sub expand_specials {
my $service = shift;
my $order = shift;
my $preproc = [];
my $single ;
# Test if already expanded
{
my $cached = $service->{"#%#expand_specials"};
if (defined $cached) {
DEBUG "[DEBUG] expand_specials(): already processed " . munin_dumpconfig_as_str($cached);
return $cached;
}
DEBUG "[DEBUG] expand_specials(): not processed, proceeding for " . munin_dumpconfig_as_str($service);
}
# we have to compute the result;
my $result = [];
my $fieldnum = 0;
for my $field (@$order) { # Search for 'specials'...
my $tmp_field;
if ($field =~ /^-(.+)$/) { # Invisible field
$field = $1;
munin_set_var_loc($service, [$field, "graph"], "no");
}
$fieldnum++;
if ($field =~ /^([^=]+)=(.+)$/) { # Aliased in graph_order
my $fname = $1;
my $spath = $2;
my $src = munin_get_node_partialpath($service, $spath);
my $sname = munin_get_node_name($src);
if(!defined $src) {
ERROR "[ERROR] Failed to find $fname source at $spath, skipping field";
next;
}
DEBUG "[DEBUG] Copying settings from $sname to $fname.";
foreach my $foption ("draw", "type", "rrdfile", "fieldname", "info")
{
if (!defined $service->{$fname}->{$foption}) {
if (defined $src->{$foption}) {
munin_set_var_loc($service, [$fname, $foption],
$src->{$foption});
}
}
}
if (!defined $service->{$fname}->{"label"}) {
munin_set_var_loc($service, [$fname, "label"], $fname);
}
munin_set_var_loc(
$service,
[$fname, "filename"],
munin_get_rrd_filename($src));
}
elsif (defined($tmp_field = get_stack_command($service->{$field}))) {
# Aliased with .stack
DEBUG "[DEBUG] expand_specials ($tmp_field): Doing stack...";
my @spc_stack = ();
foreach my $pre (split(/\s+/, $tmp_field)) {
(my $name = $pre) =~ s/=.+//;
# Auto selects the .draw
my $draw = (!@spc_stack) ? munin_get($service->{$field}, "draw", "LINE1") : "STACK";
munin_set_var_loc($service, [$name, "draw"], $draw);
# Don't process this field later
munin_set_var_loc($service, [$field, "process"], "0");
push(@spc_stack, $name);
push(@$preproc, $pre);
push @$result, "$name.label";
push @$result, "$name.draw";
push @$result, "$name.cdef";
munin_set_var_loc($service, [$name, "label"], $name);
munin_set_var_loc($service, [$name, "cdef"], "$name,UN,0,$name,IF");
if (munin_get($service->{$field}, "cdef")
and !munin_get_bool($service->{$name}, "onlynullcdef", 0)) {
DEBUG "[DEBUG] NotOnlynullcdef ($field)...";
$service->{$name}->{"cdef"} .= "," . $service->{$field}->{"cdef"};
$service->{$name}->{"cdef"} =~ s/\b$field\b/$name/g;
}
else {
DEBUG "[DEBUG] Onlynullcdef ($field)...";
munin_set_var_loc($service, [$name, "onlynullcdef"], 1);
push @$result, "$name.onlynullcdef";
}
}
} # if get_stack_command
elsif (defined($tmp_field = get_sum_command($service->{$field}))) {
my @spc_stack = ();
my $last_name = "";
DEBUG "[DEBUG] expand_specials ($tmp_field): Doing sum...";
if (@$order == 1
or (@$order == 2 and munin_get($field, "negative", 0))) {
$single = 1;
}
foreach my $pre (split(/\s+/, $tmp_field)) {
(my $path = $pre) =~ s/.+=//;
my $name = "z" . $fieldnum . "_" . scalar(@spc_stack);
$last_name = $name;
munin_set_var_loc($service, [$name, "cdef"],
"$name,UN,0,$name,IF");
munin_set_var_loc($service, [$name, "graph"], "no");
munin_set_var_loc($service, [$name, "label"], $name);
push @$result, "$name.cdef";
push @$result, "$name.graph";
push @$result, "$name.label";
push(@spc_stack, $name);
push(@$preproc, "$name=$pre");
}
$service->{$last_name}->{"cdef"} .=
"," . join(",$AddNAN,", @spc_stack[0 .. @spc_stack - 2]) .
",$AddNAN";
if (my $tc = munin_get($service->{$field}, "cdef", 0))
{ # Oh bugger...
DEBUG "[DEBUG] Oh bugger...($field)...\n";
$tc =~ s/\b$field\b/$service->{$last_name}->{"cdef"}/;
$service->{$last_name}->{"cdef"} = $tc;
}
munin_set_var_loc($service, [$field, "process"], "0");
munin_set_var_loc(
$service,
[$last_name, "draw"],
munin_get($service->{$field}, "draw"));
munin_set_var_loc(
$service,
[$last_name, "colour"],
munin_get($service->{$field}, "colour"));
munin_set_var_loc(
$service,
[$last_name, "label"],
munin_get($service->{$field}, "label"));
munin_set_var_loc(
$service,
[$last_name, "graph"],
munin_get($service->{$field}, "graph", "yes"));
if (my $tmp = munin_get($service->{$field}, "negative")) {
munin_set_var_loc($service, [$last_name, "negative"], $tmp);
}
munin_set_var_loc($service, [$field, "realname"], $last_name);
}
elsif (my $nf = munin_get($service->{$field}, "negative", 0)) {
if ( !munin_get_bool($service->{$nf}, "graph", 1)
or munin_get_bool($service->{$nf}, "skipdraw", 0)) {
munin_set_var_loc($service, [$nf, "graph"], "no");
}
}
} # for (@$order)
# Return & save it for future use
$service->{"#%#expand_specials"} = {
"added" => $result,
"preprocess" => $preproc,
"single" => $single,
};
return $service->{"#%#expand_specials"};
}
sub single_value {
my $service = shift;
my $graphable = munin_get($service, "graphable", 0);
if (!$graphable) {
foreach my $field (@{munin_get_field_order($service)}) {
DEBUG "[DEBUG] single_value: Checking field \"$field\".";
$graphable++ if munin_draw_field($service->{$field});
}
munin_set_var_loc($service, ["graphable"], $graphable);
}
DEBUG "[DEBUG] service "
. join(' :: ', @{munin_get_node_loc($service)})
. " has $graphable elements.";
return ($graphable == 1);
}
sub get_field_name {
my $name = shift;
$name = substr(Digest::MD5::md5_hex($name), -15)
if (length $name > 15);
return $name;
}
sub process_work {
my (@hosts) = @_;
# Make array of what is probably needed to graph
my $work_array = [];
if ($only_fqn) {
push @$work_array, munin_find_node_by_fqn($config,$only_fqn);
} elsif (@hosts) {
foreach my $nodename (@hosts) {
push @$work_array,
map {@{munin_find_field($_->{$nodename}, "graph_title")}}
@{munin_find_field($config, $nodename)};
}
} else {
FATAL "[FATAL] In process_work, no fqn and no hosts!";
}
# @$work_array contains copy of (or pointer to) each service to be graphed.
for my $service (@$work_array) {
# Want to avoid forking for that
next if (skip_service($service));
# Fork (or not) and run the anonymous sub afterwards.
fork_and_work(sub {process_service($service);});
}
}
sub process_field {
my $field = shift;
return munin_get_bool($field, "process", 1);
}
sub fork_and_work {
my ($work) = @_;
if (!$do_fork) {
# We're not forking. Do work and return.
DEBUG "[DEBUG] Doing work synchronously";
&$work;
return;
}
# Make sure we don't fork too much
while ($running >= $max_running) {
DEBUG
"[DEBUG] Too many forks ($running/$max_running), wait for something to get done";
look_for_child("block");
--$running;
}
my $pid = fork();
if (!defined $pid) {
ERROR "[ERROR] fork failed: $!";
die "fork failed: $!";
}
if ($pid == 0) {
# This block does the real work. Since we're forking exit
# afterwards.
&$work;
# See?!
exit 0;
}
else {
++$running;
DEBUG "[DEBUG] Forked: $pid. Now running $running/$max_running";
while ($running and look_for_child()) {
--$running;
}
}
}
sub remove_dups {
my @ret;
my %keys;
for my $order (@_) {
(my $name = $order) =~ s/=.+//;
push @ret, $order unless ($keys{$name} ++);
}
return @ret;
}
sub process_service {
my ($service) = @_;
# See if we should skip the service
return if (skip_service($service));
# Make my graphs
my $sname = munin_get_node_name($service);
my $skeypath = munin_get_keypath($service);
my $service_time = Time::HiRes::time;
my $lastupdate = 0;
my $now = time;
my $fnum = 0;
my @rrd;
DEBUG "[DEBUG] Node name: $sname\n";
my $field_count = 0;
my $max_field_len = 0;
my @field_order = ();
my $rrdname;
@field_order = @{munin_get_field_order($service)};
# Array to keep 'preprocess'ed fields.
DEBUG "[DEBUG] Expanding specials for $sname: \""
. join("\",\"", @field_order) . "\".";
my $expanded_result = expand_specials($service, \@field_order);
my $force_single_value = $expanded_result->{single};
my @added = @{ $expanded_result->{added} };
# put preprocessed fields in front
unshift @field_order, @{ $expanded_result->{preprocess} };
# Remove duplicates, while retaining the order
@field_order = remove_dups ( @field_order );
# Get max label length
DEBUG "[DEBUG] Checking field lengths for $sname: \"" . join('","', @field_order) . '".';
$max_field_len = munin_get_max_label_length($service, \@field_order);
# Global headers makes the value tables easier to read no matter how
# wide the labels are.
my $global_headers = 1;
# Default format for printing under graph.
my $avgformat;
my $rrdformat = $avgformat = "%6.2lf";
if (munin_get($service, "graph_args", "") =~ /--base\s+1024/) {
# If the base unit is 1024 then 1012.56 is a valid
# number to show. That's 7 positions, not 6.
$rrdformat = $avgformat = "%7.2lf";
}
# Plugin specified complete printf format
$rrdformat = munin_get($service, "graph_printf", $rrdformat);
my $rrdscale = '';
if (munin_get_bool($service, "graph_scale", 1)) {
$rrdscale = '%s';
}
# Array to keep negative data until we're finished with positive.
my @rrd_negatives = ();
my $filename;
my %total_pos;
my %total_neg;
my $autostacking = 0;
DEBUG "[DEBUG] Treating fields \"" . join("\",\"", @field_order) . "\".";
for my $fname (@field_order) {
my $path = undef;
my $field = undef;
if ($fname =~ s/=(.+)//) {
$path = $1;
}
$field = munin_get_node($service, [$fname]);
next if (!defined $field or !$field or !process_field($field));
DEBUG "[DEBUG] Processing field \"$fname\" ["
. munin_get_node_name($field) . "].";
my $fielddraw = munin_get($field, "draw", "LINE1");
if ($field_count == 0 and $fielddraw eq 'STACK') {
# Illegal -- first field is a STACK
DEBUG "ERROR: First field (\"$fname\") of graph "
. join(' :: ', munin_get_node_loc($service))
. " is STACK. STACK can only be drawn after a LINEx or AREA.";
$fielddraw = "LINE1";
}
if ($fielddraw eq 'AREASTACK') {
if ($autostacking == 0) {
$fielddraw = 'AREA';
$autostacking = 1;
}
else {
$fielddraw = 'STACK';
}
}
if ($fielddraw =~ /LINESTACK(\d+(?:.\d+)?)/) {
if ($autostacking == 0) {
$fielddraw = "LINE$1";
$autostacking = 1;
}
else {
$fielddraw = 'STACK';
}
}
# Getting name of rrd file
$filename = munin_get_rrd_filename($field, $path);
if (! $filename) {
ERROR "[ERROR] filename is empty for " . munin_dumpconfig_as_str($field) . ", $path";
# Ignore this field
next;
}
if(!defined $filename) {
ERROR "[ERROR] Failed getting filename for $path, skipping field";
next;
}
# Here it is OK to flush the rrdcached, since we'll flush it anyway
# with graph
my $update = RRDs::last(@rrdcached_params, $filename);
$update = 0 if !defined $update;
if ($update > $lastupdate) {
$lastupdate = $update;
}
# It does not look like $fieldname.rrdfield is possible to set
my $rrdfield = munin_get($field, "rrdfield", "42");
my $single_value = $force_single_value || single_value($service);
# XXX - single_value is wrong for some multigraph, disabling it for now
$single_value = 0;
my $has_negative = munin_get($field, "negative");
# Trim the fieldname to make room for other field names.
$rrdname = &get_field_name($fname);
reset_cdef($service, $rrdname);
if ($rrdname ne $fname) {
# A change was made
munin_set($field, "cdef_name", $rrdname);
}
# Push will place the DEF too far down for some CDEFs to work
unshift(@rrd, "DEF:g$rrdname=" . $filename . ":" . $rrdfield . ":AVERAGE");
unshift(@rrd, "DEF:i$rrdname=" . $filename . ":" . $rrdfield . ":MIN");
unshift(@rrd, "DEF:a$rrdname=" . $filename . ":" . $rrdfield . ":MAX");
if (munin_get_bool($field, "onlynullcdef", 0)) {
push(@rrd,
"CDEF:c$rrdname=g$rrdname"
. (($now - $update) > 900 ? ",POP,UNKN" : ""));
}
if ( munin_get($field, "type", "GAUGE") ne "GAUGE"
and graph_by_minute($service)) {
push(@rrd, expand_cdef($service, \$rrdname, "$fname,60,*"));
}
if ( munin_get($field, "type", "GAUGE") ne "GAUGE"
and graph_by_hour($service)) {
push(@rrd, expand_cdef($service, \$rrdname, "$fname,3600,*"));
}
if (my $tmpcdef = munin_get($field, "cdef")) {
push(@rrd, expand_cdef($service, \$rrdname, $tmpcdef));
push(@rrd, "CDEF:c$rrdname=g$rrdname");
DEBUG "[DEBUG] Field name after cdef set to $rrdname";
}
elsif (!munin_get_bool($field, "onlynullcdef", 0)) {
push(@rrd,
"CDEF:c$rrdname=g$rrdname"
. (($now - $update) > 900 ? ",POP,UNKN" : ""));
}
next if !munin_draw_field($field);
DEBUG "[DEBUG] Drawing field \"$fname\".";
if ($single_value) {
# Only one field. Do min/max range.
push(@rrd, "CDEF:min_max_diff=a$rrdname,i$rrdname,-");
push(@rrd, "CDEF:re_zero=min_max_diff,min_max_diff,-")
if !munin_get($field, "negative");
push(@rrd, "AREA:i$rrdname#ffffff");
push(@rrd, "STACK:min_max_diff#$range_colour");
push(@rrd, "LINE1:re_zero#000000")
if !munin_get($field, "negative");
}
# Push "global" headers or not
if ($has_negative and !@rrd_negatives and $global_headers < 2) {
# Always for -/+ graphs
push(@rrd, "COMMENT:" . (" " x $max_field_len));
push(@rrd, "COMMENT:Cur (-/+)");
push(@rrd, "COMMENT:Min (-/+)");
push(@rrd, "COMMENT:Avg (-/+)");
push(@rrd, "COMMENT:Max (-/+) \\j");
$global_headers = 2; # Avoid further headers/labels
}
elsif ($global_headers == 1) {
# Or when we want to.
push(@rrd, "COMMENT:" . (" " x $max_field_len));
push(@rrd, "COMMENT: Cur$RRDkludge:");
push(@rrd, "COMMENT:Min$RRDkludge:");
push(@rrd, "COMMENT:Avg$RRDkludge:");
push(@rrd, "COMMENT:Max$RRDkludge: \\j");
$global_headers = 2; # Avoid further headers/labels
}
my $colour = munin_get($field, "colour");
if ($colour && $colour =~ /^COLOUR(\d+)$/) {
$colour = $COLOUR[$1 % @COLOUR];
}
# Select a default colour if no explict one
$colour ||= ($single_value) ? $single_colour : $COLOUR[$field_count % @COLOUR];
my $warn_colour = $single_value ? "ff0000" : $colour;
# colour needed for transparent predictions and trends
munin_set($field, "colour", $colour);
$field_count++;
my $tmplabel = munin_get($field, "label", $fname);
# Substitute ${graph_period}
my $period = munin_get($service, "graph_period", "second");
$tmplabel =~ s/\$\{graph_period\}/$period/g;
push(@rrd,
$fielddraw
. ":g$rrdname"
. "#$colour" . ":"
. escape($tmplabel)
. (" " x ($max_field_len + 1 - length $tmplabel)));
# Check for negative fields (typically network (or disk) traffic)
if ($has_negative) {
my $negfieldname
= orig_to_cdef($service, munin_get($field, "negative"));
my $negfield = $service->{$negfieldname};
if (my $tmpneg = munin_get($negfield, "realname")) {
$negfieldname = $tmpneg;
$negfield = $service->{$negfieldname};
}
if (!@rrd_negatives) {
# zero-line, to redraw zero afterwards.
push(@rrd_negatives, "CDEF:re_zero=g$negfieldname,UN,0,0,IF");
}
push(@rrd_negatives, "CDEF:ng$negfieldname=g$negfieldname,-1,*");
if ($single_value) {
# Only one field. Do min/max range.
push(@rrd,
"CDEF:neg_min_max_diff=i$negfieldname,a$negfieldname,-");
push(@rrd, "CDEF:ni$negfieldname=i$negfieldname,-1,*");
push(@rrd, "AREA:ni$negfieldname#ffffff");
push(@rrd, "STACK:neg_min_max_diff#$range_colour");
}
push(@rrd_negatives, $fielddraw . ":ng$negfieldname#$colour");
# Draw HRULEs
my $linedef = munin_get($negfield, "line");
if ($linedef) {
my ($number, $ldcolour, $label) = split(/:/, $linedef, 3);
unshift(@rrd_negatives,
"HRULE:" . $number . ($ldcolour ? "#$ldcolour" : "#$colour"));
}
elsif (my $tmpwarn = munin_get($negfield, "warning")) {
my ($warn_min, $warn_max) = split(':', $tmpwarn,2);
if (defined($warn_min) and $warn_min ne '') {
unshift(@rrd, "HRULE:${warn_min}#${warn_colour}${RRDLineThresholdAttribute}");
}
if (defined($warn_max) and $warn_max ne '') {
unshift(@rrd, "HRULE:${warn_max}#${warn_colour}${RRDLineThresholdAttribute}");
}
}
push(@rrd,
"GPRINT:c$negfieldname:LAST:$rrdformat" . $rrdscale . "/\\g");
push(@rrd, "GPRINT:c$rrdname:LAST:$rrdformat" . $rrdscale . "");
push(@rrd,
"GPRINT:i$negfieldname:MIN:$rrdformat" . $rrdscale . "/\\g");
push(@rrd, "GPRINT:i$rrdname:MIN:$rrdformat" . $rrdscale . "");
push(@rrd,
"GPRINT:g$negfieldname:AVERAGE:$avgformat"
. $rrdscale
. "/\\g");
push(@rrd, "GPRINT:g$rrdname:AVERAGE:$avgformat" . $rrdscale . "");
push(@rrd,
"GPRINT:a$negfieldname:MAX:$rrdformat" . $rrdscale . "/\\g");
push(@rrd, "GPRINT:a$rrdname:MAX:$rrdformat" . $rrdscale . "\\j");
push(@{$total_pos{'min'}}, "i$rrdname");
push(@{$total_pos{'avg'}}, "g$rrdname");
push(@{$total_pos{'max'}}, "a$rrdname");
push(@{$total_neg{'min'}}, "i$negfieldname");
push(@{$total_neg{'avg'}}, "g$negfieldname");
push(@{$total_neg{'max'}}, "a$negfieldname");
}
else {
push(@rrd, "COMMENT: Cur$RRDkludge:") unless $global_headers;
push(@rrd, "GPRINT:c$rrdname:LAST:$rrdformat" . $rrdscale . "");
push(@rrd, "COMMENT: Min$RRDkludge:") unless $global_headers;
push(@rrd, "GPRINT:i$rrdname:MIN:$rrdformat" . $rrdscale . "");
push(@rrd, "COMMENT: Avg$RRDkludge:") unless $global_headers;
push(@rrd, "GPRINT:g$rrdname:AVERAGE:$avgformat" . $rrdscale . "");
push(@rrd, "COMMENT: Max$RRDkludge:") unless $global_headers;
push(@rrd, "GPRINT:a$rrdname:MAX:$rrdformat" . $rrdscale . "\\j");
push(@{$total_pos{'min'}}, "i$rrdname");
push(@{$total_pos{'avg'}}, "g$rrdname");
push(@{$total_pos{'max'}}, "a$rrdname");
}
# Draw HRULEs
my $linedef = munin_get($field, "line");
if ($linedef) {
my ($number, $ldcolour, $label) = split(/:/, $linedef, 3);
$label =~ s/:/\\:/g if defined $label;
unshift(
@rrd,
"HRULE:"
. $number
. (
$ldcolour ? "#$ldcolour"
: ((defined $single_value and $single_value) ? "#ff0000"
: "#$colour"))
. ((defined $label and length($label)) ? ":$label" : ""),
"COMMENT: \\j"
);
}
elsif (my $tmpwarn = munin_get($field, "warning")) {
my ($warn_min, $warn_max) = split(':', $tmpwarn,2);
if (defined($warn_min) and $warn_min ne '') {
unshift(@rrd, "HRULE:${warn_min}#${warn_colour}${RRDLineThresholdAttribute}");
}
if (defined($warn_max) and $warn_max ne '') {
unshift(@rrd, "HRULE:${warn_max}#${warn_colour}${RRDLineThresholdAttribute}");
}
}
}
my $graphtotal = munin_get($service, "graph_total");
if (@rrd_negatives) {
push(@rrd, @rrd_negatives);
push(@rrd, "LINE1:re_zero#000000"); # Redraw zero.
if ( defined $graphtotal
and exists $total_pos{'min'}
and exists $total_neg{'min'}
and @{$total_pos{'min'}}
and @{$total_neg{'min'}}) {
push(@rrd,
"CDEF:ipostotal="
. join(",", @{$total_pos{'min'}})
. (",$AddNAN" x (@{$total_pos{'min'}} - 1)));
push(@rrd,
"CDEF:gpostotal="
. join(",", @{$total_pos{'avg'}})
. (",$AddNAN" x (@{$total_pos{'avg'}} - 1)));
push(@rrd,
"CDEF:apostotal="
. join(",", @{$total_pos{'max'}})
. (",$AddNAN" x (@{$total_pos{'max'}} - 1)));
push(@rrd,
"CDEF:inegtotal="
. join(",", @{$total_neg{'min'}})
. (",$AddNAN" x (@{$total_neg{'min'}} - 1)));
push(@rrd,
"CDEF:gnegtotal="
. join(",", @{$total_neg{'avg'}})
. (",$AddNAN" x (@{$total_neg{'avg'}} - 1)));
push(@rrd,
"CDEF:anegtotal="
. join(",", @{$total_neg{'max'}})
. (",$AddNAN" x (@{$total_neg{'max'}} - 1)));
push(@rrd,
"LINE1:gpostotal#000000:$graphtotal"
. (" " x ($max_field_len - length($graphtotal) + 1)));
push(@rrd, "GPRINT:gnegtotal:LAST:$rrdformat" . $rrdscale . "/\\g");
push(@rrd, "GPRINT:gpostotal:LAST:$rrdformat" . $rrdscale . "");
push(@rrd, "GPRINT:inegtotal:MIN:$rrdformat" . $rrdscale . "/\\g");
push(@rrd, "GPRINT:ipostotal:MIN:$rrdformat" . $rrdscale . "");
push(@rrd,
"GPRINT:gnegtotal:AVERAGE:$avgformat" . $rrdscale . "/\\g");
push(@rrd, "GPRINT:gpostotal:AVERAGE:$avgformat" . $rrdscale . "");
push(@rrd, "GPRINT:anegtotal:MAX:$rrdformat" . $rrdscale . "/\\g");
push(@rrd, "GPRINT:apostotal:MAX:$rrdformat" . $rrdscale . "\\j");
}
}
elsif ( defined $graphtotal
and exists $total_pos{'min'}
and @{$total_pos{'min'}}) {
push(@rrd,
"CDEF:ipostotal="
. join(",", @{$total_pos{'min'}})
. (",$AddNAN" x (@{$total_pos{'min'}} - 1)));
push(@rrd,
"CDEF:gpostotal="
. join(",", @{$total_pos{'avg'}})
. (",$AddNAN" x (@{$total_pos{'avg'}} - 1)));
push(@rrd,
"CDEF:apostotal="
. join(",", @{$total_pos{'max'}})
. (",$AddNAN" x (@{$total_pos{'max'}} - 1)));
push(@rrd,
"LINE1:gpostotal#000000:$graphtotal"
. (" " x ($max_field_len - length($graphtotal) + 1)));
push(@rrd, "COMMENT: Cur$RRDkludge:") unless $global_headers;
push(@rrd, "GPRINT:gpostotal:LAST:$rrdformat" . $rrdscale . "");
push(@rrd, "COMMENT: Min$RRDkludge:") unless $global_headers;
push(@rrd, "GPRINT:ipostotal:MIN:$rrdformat" . $rrdscale . "");
push(@rrd, "COMMENT: Avg$RRDkludge:") unless $global_headers;
push(@rrd, "GPRINT:gpostotal:AVERAGE:$avgformat" . $rrdscale . "");
push(@rrd, "COMMENT: Max$RRDkludge:") unless $global_headers;
push(@rrd, "GPRINT:apostotal:MAX:$rrdformat" . $rrdscale . "\\j");
}
# insert these graph args in the end
if (defined(my $tmp_field = get_custom_graph_args_after($service))) {
push(@rrd, @{$tmp_field});
}
my $nb_graphs_drawn = 0;
for my $time (keys %times) {
next unless ($draw{$time});
my $picfilename = get_picture_filename($service, $time);
DEBUG "[DEBUG] Looking into drawing $picfilename";
(my $picdirname = $picfilename) =~ s/\/[^\/]+$//;
DEBUG "[DEBUG] Picture filename: $picfilename";
my @complete = get_fonts();
# Watermarks introduced in RRD 1.2.13.
push(@complete, '-W', $watermark) if $RRDs::VERSION >= 1.2013;
# Do the header (title, vtitle, size, etc...), but IN THE BEGINNING
unshift @complete, @{get_header($service, $time)};
if ($LINEkluge) {
@rrd = map {
my $line = $_;
$line =~ s/LINE3:/LINE2.2:/;
$line =~ s/LINE2:/LINE1.6:/;
# LINE1 is thin enough.
$line;
} @rrd;
}
push @complete, @rrd;
# graph end in future
push (@complete, handle_trends($time, $lastupdate, $pinpoint, $service, $RRDkludge, @added));
# Make sure directory exists
munin_mkdir_p($picdirname, oct(777));
# Since version 1.3 rrdtool uses libpango which needs its input
# as utf8 string. So we assume that every input is in latin1
# and decode it to perl's internal representation and then to utf8.
if ($RRDs::VERSION >= 1.3) {
@complete = map {
my $str = $_;
my $utf8 = guess_encoding($str, 'utf8');
ref $utf8 ? $str : encode("utf8", (decode("latin1", $_)));
} @complete;
}
# Surcharging the graphing limits
my ($upper_limit_overrided, $lower_limit_overrided);
for (my $index = 0; $index <= $#complete; $index++) {
if ($complete[$index] =~ /^(--upper-limit|-u)$/ && (defined $upper_limit)) {
$upper_limit = get_scientific($upper_limit);
$complete[$index + 1] = $upper_limit;
$upper_limit_overrided = 1;
}
if ($complete[$index] =~ /^(--lower-limit|-l)$/ && (defined $lower_limit)) {
$lower_limit = get_scientific($lower_limit);
$complete[$index + 1] = $lower_limit;
$lower_limit_overrided = 1;
}
}
# Add the limit if not present
if (defined $upper_limit && ! $upper_limit_overrided) {
push @complete, "--upper-limit", $upper_limit;
}
if (defined $lower_limit && ! $lower_limit_overrided) {
push @complete, "--lower-limit", $lower_limit;
}
DEBUG "\n\nrrdtool 'graph' '" . join("' \\\n\t'", @rrdcached_params, @complete) . "'\n";
$nb_graphs_drawn ++;
RRDs::graph(@rrdcached_params, @complete);
if (my $ERROR = RRDs::error) {
ERROR "[RRD ERROR] Unable to graph $picfilename : $ERROR";
# ALWAYS dumps the cmd used when an error occurs.
# Otherwise, it will be difficult to debug post-mortem
ERROR "[RRD ERROR] rrdtool 'graph' '" . join("' \\\n\t'", @rrdcached_params, @complete) . "'\n";
}
elsif (!-f $picfilename) {
ERROR "[RRD ERROR] rrdtool graph did not generate the image (make sure there are data to graph).\n";
}
else {
# Set time of png file to the time of the last update of
# the rrd file. This makes http's If-Modified-Since more
# reliable, esp. in combination with munin-*cgi-graph.
# Since this disrupts rrd's --lazy option we're disableing
# it unless we (munin-graph) were specially asked --lazy.
# This way --lazy continues to work as expected, and since
# CGI uses --nolazy, http IMS are also working as expected.
if (! $force_lazy) {
DEBUG "[DEBUG] setting time on $picfilename";
utime $lastupdate, $lastupdate, $picfilename;
}
if ($list_images) {
# Command-line option to list images created
print $picfilename. "\n";
}
}
}
if (munin_get_bool($service, "graph_sums", 0)) {
foreach my $time (keys %sumtimes) {
my $picfilename = get_picture_filename($service, $time, 1);
DEBUG "Looking into drawing $picfilename";
(my $picdirname = $picfilename) =~ s/\/[^\/]+$//;
next unless ($draw{"sum" . $time});
my @rrd_sum;
push @rrd_sum, @{get_header($service, $time, 1)};
# graph end in future
push (@rrd_sum, handle_trends($time, $lastupdate, $pinpoint, $service, $RRDkludge, @added));
push @rrd_sum, @rrd;
my $labelled = 0;
my @defined = ();
for (my $index = 0; $index <= $#rrd_sum; $index++) {
if ($rrd_sum[$index] =~ /^(--vertical-label|-v)$/) {
(my $label = munin_get($service, "graph_vlabel"))
=~ s/\$\{graph_period\}/$sumtimes{$time}[0]/g;
splice(@rrd_sum, $index, 2, ("--vertical-label", $label));
$index++;
$labelled++;
}
elsif ($rrd_sum[$index]
=~ /^(LINE[123]|STACK|AREA|GPRINT):([^#:]+)([#:].+)$/) {
my ($pre, $fname, $post) = ($1, $2, $3);
next if $fname eq "re_zero";
if ($post =~ /^:AVERAGE/) {
splice(@rrd_sum, $index, 1, $pre . ":x$fname" . $post);
$index++;
next;
}
next if grep /^x$fname$/, @defined;
push @defined, "x$fname";
my @replace;
if (munin_get($service->{$fname}, "type", "GAUGE") ne
"GAUGE") {
if ($time eq "week") {
# Every plot is half an hour. Add two plots and multiply, to get per hour
if (graph_by_minute($service)) {
# Already multiplied by 60
push @replace,
"CDEF:x$fname=PREV($fname),UN,0,PREV($fname),IF,$fname,+,5,*,6,*";
} elsif (graph_by_hour($service)) {
# Already multiplied by 3600, have to *divide* by 12
push @replace,
"CDEF:x$fname=PREV($fname),UN,0,PREV($fname),IF,$fname,+,12,/,6,*";
}
else {
push @replace,
"CDEF:x$fname=PREV($fname),UN,0,PREV($fname),IF,$fname,+,300,*,6,*";
}
}
else {
# Every plot is one day exactly. Just multiply.
if (graph_by_minute($service)) {
# Already multiplied by 60
push @replace, "CDEF:x$fname=$fname,5,*,288,*";
} elsif (graph_by_hour($service)) {
# Already multiplied by 3600, have to *divide* by 12
push @replace, "CDEF:x$fname=$fname,12,/,288,*";
}
else {
push @replace,
"CDEF:x$fname=$fname,300,*,288,*";
}
}
}
push @replace, $pre . ":x$fname" . $post;
splice(@rrd_sum, $index, 1, @replace);
$index++;
}
elsif (
$rrd_sum[$index] =~ /^(--lower-limit|--upper-limit|-l|-u)$/)
{
$index++;
$rrd_sum[$index]
= $rrd_sum[$index] * 300 * $sumtimes{$time}->[1];
}
}
unless ($labelled) {
my $label = munin_get($service, "graph_vlabel_sum_$time",
$sumtimes{$time}->[0]);
unshift @rrd_sum, "--vertical-label", $label;
}
DEBUG "[DEBUG] \n\nrrdtool graph '" . join("' \\\n\t'", @rrd_sum) . "'\n";
# Make sure directory exists
munin_mkdir_p($picdirname, oct(777));
$nb_graphs_drawn ++;
RRDs::graph(@rrdcached_params, @rrd_sum);
if (my $ERROR = RRDs::error) {
ERROR "[RRD ERROR(sum)] Unable to graph "
. get_picture_filename($service, $time)
. ": $ERROR";
}
elsif ($list_images) {
# Command-line option to list images created
print get_picture_filename ($service, $time, 1), "\n";
}
} # foreach (keys %sumtimes)
} # if graph_sums
$service_time = sprintf("%.2f", (Time::HiRes::time - $service_time));
DEBUG "[DEBUG] Graphed service $skeypath ($service_time sec for $nb_graphs_drawn graphs)";
print $STATS "GS|$service_time\n" unless $skip_stats;
foreach (@added) {
delete $service->{$_} if exists $service->{$_};
}
@added = ();
}
# sets enddate --end and draws a vertical ruler if enddate is in the future
# future trends are also added to the graph here
sub handle_trends {
my $time = shift;
my $lastupdate = shift;
my $pinpoint = shift;
my $service = shift;
my $RRDkludge = shift;
my @added = @_;
my @complete;
# enddate possibly in future
my $futuretime = $pinpoint ? 0 : $resolutions{$time} * get_end_offset($service);
my $enddate = time + ($futuretime);
DEBUG "[DEBUG] lastupdate: $lastupdate, enddate: $enddate\n";
# future begins at this horizontal ruler
if ($enddate > $lastupdate) {
push(@complete, "VRULE:$lastupdate#999999");
}
# create trends/predictions
foreach my $field (@{munin_find_field($service, "label")}) {
my $fieldname = munin_get_node_name($field);
my $colour = $single_colour;
# Skip virtual fieldnames, otherwise beware of $hash->{foo}{bar}.
#
# On a sidenote, what's the output of the following code ?
# perl -e '$a = {}; if ($a->{foo}{bar}) { print "Found Foo/Bar\n"; } \
# if ($a->{foo}) { print "Found Foo\n"; }'
next if ! defined $service->{$fieldname};
if (defined $service->{$fieldname}{'colour'}) {
$colour = "$service->{$fieldname}{'colour'}66";
}
my $rrd_fieldname = get_field_name($fieldname);
my $cdef = "";
if (defined $service->{$fieldname}{'cdef'}) {
$cdef = "cdef";
}
#trends
if (defined $service->{$fieldname}{'trend'} and $service->{$fieldname}{'trend'} eq 'yes') {
push (@complete, "CDEF:t$rrd_fieldname=c$cdef$rrd_fieldname,$futuretime,TRENDNAN");
push (@complete, "LINE1:t$rrd_fieldname#$colour:$service->{$fieldname}->{'label'} trend\\l");
DEBUG "[DEBUG] set trend for $fieldname\n";
}
#predictions
if (defined $service->{$fieldname}{'predict'}) {
#arguments: pattern length (e.g. 1 day), smoothing (multiplied with timeresolution)
my @predict = split(",", $service->{$fieldname}{'predict'});
my $predictiontime = int ($futuretime / $predict[0]) + 2; #2 needed for 1 day
my $smooth = $predict[1]*$resolutions{$time};
push (@complete, "CDEF:p$rrd_fieldname=$predict[0],-$predictiontime,$smooth,c$cdef$rrd_fieldname,PREDICT");
push (@complete, "LINE1:p$rrd_fieldname#$colour:$service->{$fieldname}->{'label'} prediction\\l");
DEBUG "[DEBUG] set prediction for $fieldname\n";
}
}
push(@complete,
"COMMENT:Last update$RRDkludge: "
. RRDescape(scalar localtime($lastupdate))
. "\\r");
# If pinpointing, --end should *NOT* be changed
if (! $pinpoint) {
if (@added) { # stop one period earlier if it's a .sum or .stack
push @complete, "--end",
(int(($enddate-$resolutions{$time}) / $resolutions{$time})) * $resolutions{$time};
} else {
push @complete, "--end",
(int($enddate / $resolutions{$time})) * $resolutions{$time};
}
}
return @complete;
}
sub get_fonts {
# Set up rrdtool graph font options according to RRD version.
my @options;
if ($RRDs::VERSION < 1.2) {
# RRD before 1.2, no font options
} elsif ($RRDs::VERSION < 1.3) {
# RRD 1.2
# The RRD 1.2 documentation says you can identify font family
# names but I never got that to work, but full font path worked
@options = (
'--font', "LEGEND:7:$libdir/DejaVuSansMono.ttf",
'--font', "UNIT:7:$libdir/DejaVuSans.ttf",
'--font', "AXIS:7:$libdir/DejaVuSans.ttf",
);
} else {
# At least 1.3
@options = (
'--font', 'DEFAULT:0:DejaVuSans,DejaVu Sans,DejaVu LGC Sans,Bitstream Vera Sans',
'--font', 'LEGEND:7:DejaVuSansMono,DejaVu Sans Mono,DejaVu LGC Sans Mono,Bitstream Vera Sans Mono,monospace',
# Colors coordinated with CSS.
'--color', 'BACK#F0F0F0', # Area around the graph
'--color', 'FRAME#F0F0F0', # Line around legend spot
'--color', 'CANVAS#FFFFFF', # Graph background, max contrast
'--color', 'FONT#666666', # Some kind of gray
'--color', 'AXIS#CFD6F8', # And axis like html boxes
'--color', 'ARROW#CFD6F8', # And arrow, ditto.
);
}
if ($RRDs::VERSION >= 1.4) {
# RRD 1.4 has border, adding it
push @options, (
'--border', '0',
);
}
return @options;
};
sub graph_by_minute {
my $service = shift;
return (munin_get($service, "graph_period", "second") eq "minute");
}
sub graph_by_hour {
my $service = shift;
return (munin_get($service, "graph_period", "second") eq "hour");
}
sub orig_to_cdef {
my $service = shift;
my $fieldname = shift;
return unless ref($service) eq "HASH";
if (defined $service->{$fieldname} && defined $service->{$fieldname}->{"cdef_name"}) {
return orig_to_cdef($service, $service->{$fieldname}->{"cdef_name"});
}
return $fieldname;
}
sub reset_cdef {
my $service = shift;
my $fieldname = shift;
return unless ref($service) eq "HASH";
if (defined $service->{$fieldname} && defined $service->{$fieldname}->{"cdef_name"}) {
reset_cdef($service, $service->{$fieldname}->{"cdef_name"});
delete $service->{$fieldname}->{"cdef_name"};
}
}
sub ends_with {
my ($src, $searched) = @_;
my $is_ending = (substr($src, - length($searched)) eq $searched);
return $is_ending;
}
sub skip_service {
my $service = shift;
my $fqn = munin_get_node_fqn($service);
# Skip if we've limited services with the omnipotent cli option only-fqn
return 1 if ($only_fqn and ! ends_with($fqn, $only_fqn));
DEBUG "[DEBUG] $fqn is in ($only_fqn)\n";
# Skip if we've limited services with cli options
return 1
if (@limit_services and
! (grep { ends_with($fqn, $_) } @limit_services));
DEBUG "[DEBUG] $fqn is in (" . join(",", @limit_services) . ")\n";
# Always graph if --force is present
return 0 if $force_graphing;
# See if we should skip it because of conf-options
return 1
if (munin_get($service, "graph", "yes") eq "on-demand"
or !munin_get_bool($service, "graph", 1));
# Don't skip
return 0;
}
sub expand_cdef {
my $service = shift;
my $cfield_ref = shift;
my $cdef = shift;
my $new_field = &get_field_name("cdef$$cfield_ref");
my ($max, $min, $avg) = (
"CDEF:a$new_field=$cdef", "CDEF:i$new_field=$cdef",
"CDEF:g$new_field=$cdef"
);
foreach my $field (@{munin_find_field($service, "label")}) {
my $fieldname = munin_get_node_name($field);
my $rrdname = &orig_to_cdef($service, $fieldname);
if ($cdef =~ /\b$fieldname\b/) {
$max =~ s/(?<=[,=(])$fieldname(?=[,=)]|$)/a$rrdname/g;
$min =~ s/(?<=[,=(])$fieldname(?=[,=)]|$)/i$rrdname/g;
$avg =~ s/(?<=[,=(])$fieldname(?=[,=)]|$)/g$rrdname/g;
}
}
munin_set_var_loc($service, [$$cfield_ref, "cdef_name"], $new_field);
$$cfield_ref = $new_field;
return ($max, $min, $avg);
}
sub parse_path {
my ($path, $domain, $node, $service, $field) = @_;
my $filename = "unknown";
if ($path =~ /^\s*([^:]*):([^:]*):([^:]*):([^:]*)\s*$/) {
$filename = munin_get_filename($config, $1, $2, $3, $4);
}
elsif ($path =~ /^\s*([^:]*):([^:]*):([^:]*)\s*$/) {
$filename = munin_get_filename($config, $domain, $1, $2, $3);
}
elsif ($path =~ /^\s*([^:]*):([^:]*)\s*$/) {
$filename = munin_get_filename($config, $domain, $node, $1, $2);
}
elsif ($path =~ /^\s*([^:]*)\s*$/) {
$filename = munin_get_filename($config, $domain, $node, $service, $1);
}
return $filename;
}
# Wrapper for munin_get_picture_filename to handle pinpoint
sub get_picture_filename {
my $of;
if (defined $output_file) { $of=$output_file; goto exit_label; }
$of = munin_get_picture_filename(@_);
exit_label:
return $of;
}
sub escape {
my $text = shift;
return if not defined $text;
$text =~ s/\\/\\\\/g;
$text =~ s/:/\\:/g;
return $text;
}
sub get_scientific {
my $value = shift;
$value =~ s/m/e-03/;
$value =~ s/k/e+03/;
$value =~ s/M/e+06/;
$value =~ s/G/e+09/;
return $value;
}
sub RRDescape {
my $text = shift;
return $RRDs::VERSION < 1.2 ? $text : escape($text);
}
sub print_usage_and_exit {
print "Usage: $0 [options]
Options:
--[no]fork Do not fork. By default munin-graph forks sub
processes for drawing graphs to utilize available
cores and I/O bandwidth. [--fork]
--n n Max number of concurrent processes [$max_running]
--[no]force Force drawing of graphs that are not usually
drawn due to options in the config file. [--noforce]
--[no]lazy Only redraw graphs when needed. [--lazy]
--help View this message.
--version View version information.
--debug View debug messages.
--[no]cron Behave as expected when run from cron. (Used internally
in Munin.)
--host <host> Limit graphed hosts to <host>. Multiple --host options
may be supplied.
--only-fqn <FQN> For internal use with CGI graphing. Graph only a
single fully qualified named graph, e.g. --only-fqn
root/Backend/dafnes.example.com/diskstats_iops
Always use with the correct --host option.
--config <file> Use <file> as configuration file. [$conffile]
--[no]list-images List the filenames of the images created.
[--nolist-images]
--output-file -o Output graph file. (used for CGI graphing)
--log-file -l Output log file. (used for CGI graphing)
--[no]day Create day-graphs. [--day]
--[no]week Create week-graphs. [--week]
--[no]month Create month-graphs. [--month]
--[no]year Create year-graphs. [--year]
--[no]sumweek Create summarised week-graphs. [--summweek]
--[no]sumyear Create summarised year-graphs. [--sumyear]
--pinpoint <start,stop> Create custom-graphs. <start,stop> is the standard unix Epoch. [not active]
--size_x <pixels> Sets the X size of the graph in pixels [175]
--size_y <pixels> Sets the Y size of the graph in pixels [400]
--lower_limit <lim> Sets the lower limit of the graph
--upper_limit <lim> Sets the upper limit of the graph
NOTE! --pinpoint and --only-fqn must not be combined with
--[no]<day|week|month|year> options. The result of doing that is
undefined.
";
exit 0;
}
1;