mirror of https://github.com/sipwise/collectd.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
727 lines
13 KiB
727 lines
13 KiB
package Collectd::Graph::Common;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use vars (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue));
|
|
|
|
use Collectd::Unixsock ();
|
|
use Carp (qw(confess cluck));
|
|
use CGI (':cgi');
|
|
use Exporter;
|
|
use Collectd::Graph::Config (qw(gc_get_scalar));
|
|
|
|
$ColorCanvas = 'FFFFFF';
|
|
$ColorFullBlue = '0000FF';
|
|
$ColorHalfBlue = 'B7B7F7';
|
|
|
|
@Collectd::Graph::Common::ISA = ('Exporter');
|
|
@Collectd::Graph::Common::EXPORT_OK = (qw(
|
|
$ColorCanvas
|
|
$ColorFullBlue
|
|
$ColorHalfBlue
|
|
|
|
sanitize_hostname
|
|
sanitize_plugin sanitize_plugin_instance
|
|
sanitize_type sanitize_type_instance
|
|
group_files_by_plugin_instance
|
|
get_files_from_directory
|
|
filename_to_ident
|
|
ident_to_filename
|
|
ident_to_string
|
|
get_all_hosts
|
|
get_files_for_host
|
|
get_files_by_ident
|
|
get_selected_files
|
|
get_timespan_selection
|
|
get_host_selection
|
|
get_plugin_selection
|
|
get_random_color
|
|
get_faded_color
|
|
sort_idents_by_type_instance
|
|
type_to_module_name
|
|
epoch_to_rfc1123
|
|
flush_files
|
|
));
|
|
|
|
our $DefaultDataDir = '/var/lib/collectd/rrd';
|
|
|
|
return (1);
|
|
|
|
sub _sanitize_generic_allow_minus
|
|
{
|
|
my $str = "" . shift;
|
|
|
|
# remove all slashes
|
|
$str =~ s#/##g;
|
|
|
|
# remove all dots and dashes at the beginning and at the end.
|
|
$str =~ s#^[\.-]+##;
|
|
$str =~ s#[\.-]+$##;
|
|
|
|
return ($str);
|
|
}
|
|
|
|
sub _sanitize_generic_no_minus
|
|
{
|
|
# Do everything the allow-minus variant does..
|
|
my $str = _sanitize_generic_allow_minus (@_);
|
|
|
|
# .. and remove the dashes, too
|
|
$str =~ s#/##g;
|
|
|
|
return ($str);
|
|
} # _sanitize_generic_no_minus
|
|
|
|
sub sanitize_hostname
|
|
{
|
|
return (_sanitize_generic_allow_minus (@_));
|
|
}
|
|
|
|
sub sanitize_plugin
|
|
{
|
|
return (_sanitize_generic_no_minus (@_));
|
|
}
|
|
|
|
sub sanitize_plugin_instance
|
|
{
|
|
return (_sanitize_generic_allow_minus (@_));
|
|
}
|
|
|
|
sub sanitize_type
|
|
{
|
|
return (_sanitize_generic_no_minus (@_));
|
|
}
|
|
|
|
sub sanitize_type_instance
|
|
{
|
|
return (_sanitize_generic_allow_minus (@_));
|
|
}
|
|
|
|
sub group_files_by_plugin_instance
|
|
{
|
|
my @files = @_;
|
|
my $data = {};
|
|
|
|
for (my $i = 0; $i < @files; $i++)
|
|
{
|
|
my $file = $files[$i];
|
|
my $key = $file->{'plugin_instance'} || '';
|
|
|
|
$data->{$key} ||= [];
|
|
push (@{$data->{$key}}, $file);
|
|
}
|
|
|
|
return ($data);
|
|
}
|
|
|
|
sub filename_to_ident
|
|
{
|
|
my $file = shift;
|
|
my $ret;
|
|
|
|
if ($file =~ m#([^/]+)/([^/\-]+)(?:-([^/]+))?/([^/\-]+)(?:-([^/]+))?\.rrd$#)
|
|
{
|
|
$ret = {hostname => $1, plugin => $2, type => $4};
|
|
if (defined ($3))
|
|
{
|
|
$ret->{'plugin_instance'} = $3;
|
|
}
|
|
if (defined ($5))
|
|
{
|
|
$ret->{'type_instance'} = $5;
|
|
}
|
|
if ($`)
|
|
{
|
|
$ret->{'_prefix'} = $`;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
return ($ret);
|
|
} # filename_to_ident
|
|
|
|
sub ident_to_filename
|
|
{
|
|
my $ident = shift;
|
|
my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
|
|
|
|
my $ret = '';
|
|
|
|
if (defined ($ident->{'_prefix'}))
|
|
{
|
|
$ret .= $ident->{'_prefix'};
|
|
}
|
|
else
|
|
{
|
|
$ret .= "$data_dir/";
|
|
}
|
|
|
|
if (!$ident->{'hostname'})
|
|
{
|
|
cluck ("hostname is undefined")
|
|
}
|
|
if (!$ident->{'plugin'})
|
|
{
|
|
cluck ("plugin is undefined")
|
|
}
|
|
if (!$ident->{'type'})
|
|
{
|
|
cluck ("type is undefined")
|
|
}
|
|
|
|
$ret .= $ident->{'hostname'} . '/' . $ident->{'plugin'};
|
|
if (defined ($ident->{'plugin_instance'}))
|
|
{
|
|
$ret .= '-' . $ident->{'plugin_instance'};
|
|
}
|
|
|
|
$ret .= '/' . $ident->{'type'};
|
|
if (defined ($ident->{'type_instance'}))
|
|
{
|
|
$ret .= '-' . $ident->{'type_instance'};
|
|
}
|
|
$ret .= '.rrd';
|
|
|
|
return ($ret);
|
|
} # ident_to_filename
|
|
|
|
sub ident_to_string
|
|
{
|
|
my $ident = shift;
|
|
|
|
my $ret = '';
|
|
|
|
$ret .= $ident->{'hostname'} . '/' . $ident->{'plugin'};
|
|
if (defined ($ident->{'plugin_instance'}))
|
|
{
|
|
$ret .= '-' . $ident->{'plugin_instance'};
|
|
}
|
|
|
|
$ret .= '/' . $ident->{'type'};
|
|
if (defined ($ident->{'type_instance'}))
|
|
{
|
|
$ret .= '-' . $ident->{'type_instance'};
|
|
}
|
|
|
|
return ($ret);
|
|
} # ident_to_string
|
|
|
|
sub get_files_from_directory
|
|
{
|
|
my $dir = shift;
|
|
my $recursive = @_ ? shift : 0;
|
|
my $dh;
|
|
my @directories = ();
|
|
my @files = ();
|
|
my $ret = [];
|
|
|
|
opendir ($dh, $dir) or die ("opendir ($dir): $!");
|
|
while (my $entry = readdir ($dh))
|
|
{
|
|
next if ($entry =~ m/^\./);
|
|
|
|
$entry = "$dir/$entry";
|
|
|
|
if (-d $entry)
|
|
{
|
|
push (@directories, $entry);
|
|
}
|
|
elsif (-f $entry)
|
|
{
|
|
push (@files, $entry);
|
|
}
|
|
}
|
|
closedir ($dh);
|
|
|
|
push (@$ret, map { filename_to_ident ($_) } sort (@files));
|
|
|
|
if ($recursive > 0)
|
|
{
|
|
for (@directories)
|
|
{
|
|
my $temp = get_files_from_directory ($_, $recursive - 1);
|
|
if ($temp && @$temp)
|
|
{
|
|
push (@$ret, @$temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ($ret);
|
|
} # get_files_from_directory
|
|
|
|
sub get_all_hosts
|
|
{
|
|
my $dh;
|
|
my @ret = ();
|
|
my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
|
|
|
|
opendir ($dh, "$data_dir") or confess ("opendir ($data_dir): $!");
|
|
while (my $entry = readdir ($dh))
|
|
{
|
|
next if ($entry =~ m/^\./);
|
|
next if (!-d "$data_dir/$entry");
|
|
next if (!-r "$data_dir/$entry" or !-x "$data_dir/$entry");
|
|
push (@ret, sanitize_hostname ($entry));
|
|
}
|
|
closedir ($dh);
|
|
|
|
if (wantarray ())
|
|
{
|
|
return (@ret);
|
|
}
|
|
elsif (@ret)
|
|
{
|
|
return (\@ret);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
} # get_all_hosts
|
|
|
|
sub get_all_plugins
|
|
{
|
|
my @hosts = @_;
|
|
my $ret = {};
|
|
my $dh;
|
|
my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
|
|
|
|
if (!@hosts)
|
|
{
|
|
@hosts = get_all_hosts ();
|
|
}
|
|
|
|
for (@hosts)
|
|
{
|
|
my $host = $_;
|
|
opendir ($dh, "$data_dir/$host") or next;
|
|
while (my $entry = readdir ($dh))
|
|
{
|
|
my $plugin;
|
|
my $plugin_instance = '';
|
|
|
|
next if ($entry =~ m/^\./);
|
|
next if (!-d "$data_dir/$host/$entry");
|
|
|
|
if ($entry =~ m#^([^-]+)-(.+)$#)
|
|
{
|
|
$plugin = $1;
|
|
$plugin_instance = $2;
|
|
}
|
|
elsif ($entry =~ m#^([^-]+)$#)
|
|
{
|
|
$plugin = $1;
|
|
$plugin_instance = '';
|
|
}
|
|
else
|
|
{
|
|
next;
|
|
}
|
|
|
|
$ret->{$plugin} ||= {};
|
|
$ret->{$plugin}{$plugin_instance} = 1;
|
|
} # while (readdir)
|
|
closedir ($dh);
|
|
} # for (@hosts)
|
|
|
|
if (wantarray ())
|
|
{
|
|
return (sort (keys %$ret));
|
|
}
|
|
else
|
|
{
|
|
return ($ret);
|
|
}
|
|
} # get_all_plugins
|
|
|
|
sub get_files_for_host
|
|
{
|
|
my $host = sanitize_hostname (shift);
|
|
my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
|
|
return (get_files_from_directory ("$data_dir/$host", 2));
|
|
} # get_files_for_host
|
|
|
|
sub _filter_ident
|
|
{
|
|
my $filter = shift;
|
|
my $ident = shift;
|
|
|
|
for (qw(hostname plugin plugin_instance type type_instance))
|
|
{
|
|
my $part = $_;
|
|
my $tmp;
|
|
|
|
if (!defined ($filter->{$part}))
|
|
{
|
|
next;
|
|
}
|
|
if (!defined ($ident->{$part}))
|
|
{
|
|
return (1);
|
|
}
|
|
|
|
if (ref $filter->{$part})
|
|
{
|
|
if (!grep { $ident->{$part} eq $_ } (@{$filter->{$part}}))
|
|
{
|
|
return (1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($ident->{$part} ne $filter->{$part})
|
|
{
|
|
return (1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
} # _filter_ident
|
|
|
|
sub get_files_by_ident
|
|
{
|
|
my $ident = shift;
|
|
my $all_files;
|
|
my @ret = ();
|
|
my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
|
|
|
|
#if ($ident->{'hostname'})
|
|
#{
|
|
#$all_files = get_files_for_host ($ident->{'hostname'});
|
|
#}
|
|
#else
|
|
#{
|
|
$all_files = get_files_from_directory ($data_dir, 3);
|
|
#}
|
|
|
|
@ret = grep { _filter_ident ($ident, $_) == 0 } (@$all_files);
|
|
|
|
return (\@ret);
|
|
} # get_files_by_ident
|
|
|
|
sub get_selected_files
|
|
{
|
|
my $ident = {};
|
|
|
|
for (qw(hostname plugin plugin_instance type type_instance))
|
|
{
|
|
my $part = $_;
|
|
my @temp = param ($part);
|
|
if (!@temp)
|
|
{
|
|
next;
|
|
}
|
|
elsif (($part eq 'plugin') || ($part eq 'type'))
|
|
{
|
|
$ident->{$part} = [map { _sanitize_generic_no_minus ($_) } (@temp)];
|
|
}
|
|
else
|
|
{
|
|
$ident->{$part} = [map { _sanitize_generic_allow_minus ($_) } (@temp)];
|
|
}
|
|
}
|
|
|
|
return (get_files_by_ident ($ident));
|
|
} # get_selected_files
|
|
|
|
sub get_timespan_selection
|
|
{
|
|
my $ret = 86400;
|
|
if (param ('timespan'))
|
|
{
|
|
my $temp = int (param ('timespan'));
|
|
if ($temp && ($temp > 0))
|
|
{
|
|
$ret = $temp;
|
|
}
|
|
}
|
|
|
|
return ($ret);
|
|
} # get_timespan_selection
|
|
|
|
sub get_host_selection
|
|
{
|
|
my %ret = ();
|
|
|
|
for (get_all_hosts ())
|
|
{
|
|
$ret{$_} = 0;
|
|
}
|
|
|
|
for (param ('hostname'))
|
|
{
|
|
my $host = _sanitize_generic_allow_minus ($_);
|
|
if (defined ($ret{$host}))
|
|
{
|
|
$ret{$host} = 1;
|
|
}
|
|
}
|
|
|
|
if (wantarray ())
|
|
{
|
|
return (grep { $ret{$_} > 0 } (sort (keys %ret)));
|
|
}
|
|
else
|
|
{
|
|
return (\%ret);
|
|
}
|
|
} # get_host_selection
|
|
|
|
sub get_plugin_selection
|
|
{
|
|
my %ret = ();
|
|
my @hosts = get_host_selection ();
|
|
|
|
for (get_all_plugins (@hosts))
|
|
{
|
|
$ret{$_} = 0;
|
|
}
|
|
|
|
for (param ('plugin'))
|
|
{
|
|
if (defined ($ret{$_}))
|
|
{
|
|
$ret{$_} = 1;
|
|
}
|
|
}
|
|
|
|
if (wantarray ())
|
|
{
|
|
return (grep { $ret{$_} > 0 } (sort (keys %ret)));
|
|
}
|
|
else
|
|
{
|
|
return (\%ret);
|
|
}
|
|
} # get_plugin_selection
|
|
|
|
sub _string_to_color
|
|
{
|
|
my $color = shift;
|
|
if ($color =~ m/([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])/)
|
|
{
|
|
return ([hex ($1) / 255.0, hex ($2) / 255.0, hex ($3) / 255.0]);
|
|
}
|
|
return;
|
|
} # _string_to_color
|
|
|
|
sub _color_to_string
|
|
{
|
|
confess ("Wrong number of arguments") if (@_ != 1);
|
|
return (sprintf ('%02hx%02hx%02hx', map { int (255.0 * $_) } @{$_[0]}));
|
|
} # _color_to_string
|
|
|
|
sub get_random_color
|
|
{
|
|
my ($r, $g, $b) = (rand (), rand ());
|
|
my $min = 0.0;
|
|
my $max = 1.0;
|
|
|
|
if (($r + $g) < 1.0)
|
|
{
|
|
$min = 1.0 - ($r + $g);
|
|
}
|
|
else
|
|
{
|
|
$max = 2.0 - ($r + $g);
|
|
}
|
|
|
|
$b = $min + (rand () * ($max - $min));
|
|
|
|
return (_color_to_string ([$r, $g, $b]));
|
|
} # get_random_color
|
|
|
|
sub get_faded_color
|
|
{
|
|
my $fg = shift;
|
|
my $bg;
|
|
my %opts = @_;
|
|
my $ret = [undef, undef, undef];
|
|
|
|
$opts{'background'} ||= [1.0, 1.0, 1.0];
|
|
$opts{'alpha'} ||= 0.25;
|
|
|
|
if (!ref ($fg))
|
|
{
|
|
$fg = _string_to_color ($fg)
|
|
or confess ("Cannot parse foreground color $fg");
|
|
}
|
|
|
|
if (!ref ($opts{'background'}))
|
|
{
|
|
$opts{'background'} = _string_to_color ($opts{'background'})
|
|
or confess ("Cannot parse background color " . $opts{'background'});
|
|
}
|
|
$bg = $opts{'background'};
|
|
|
|
for (my $i = 0; $i < 3; $i++)
|
|
{
|
|
$ret->[$i] = ($opts{'alpha'} * $fg->[$i])
|
|
+ ((1.0 - $opts{'alpha'}) * $bg->[$i]);
|
|
}
|
|
|
|
return (_color_to_string ($ret));
|
|
} # get_faded_color
|
|
|
|
sub sort_idents_by_type_instance
|
|
{
|
|
my $idents = shift;
|
|
my $array_sort = shift;
|
|
|
|
my %elements = map { $_->{'type_instance'} => $_ } (@$idents);
|
|
splice (@$idents, 0);
|
|
|
|
for (@$array_sort)
|
|
{
|
|
next if (!exists ($elements{$_}));
|
|
push (@$idents, $elements{$_});
|
|
delete ($elements{$_});
|
|
}
|
|
push (@$idents, map { $elements{$_} } (sort (keys %elements)));
|
|
} # sort_idents_by_type_instance
|
|
|
|
sub type_to_module_name
|
|
{
|
|
my $type = shift;
|
|
my $ret;
|
|
|
|
$ret = ucfirst (lc ($type));
|
|
|
|
$ret =~ s/[^A-Za-z_]//g;
|
|
$ret =~ s/_([A-Za-z])/\U$1\E/g;
|
|
|
|
return ("Collectd::Graph::Type::$ret");
|
|
} # type_to_module_name
|
|
|
|
sub epoch_to_rfc1123
|
|
{
|
|
my @days = (qw(Sun Mon Tue Wed Thu Fri Sat));
|
|
my @months = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec));
|
|
|
|
my $epoch = @_ ? shift : time ();
|
|
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
|
|
my $string = sprintf ('%s, %02d %s %4d %02d:%02d:%02d GMT', $days[$wday], $mday,
|
|
$months[$mon], 1900 + $year, $hour ,$min, $sec);
|
|
return ($string);
|
|
}
|
|
|
|
sub flush_files
|
|
{
|
|
my $all_files = shift;
|
|
my %opts = @_;
|
|
|
|
my $begin;
|
|
my $end;
|
|
my $addr;
|
|
my $interval;
|
|
my $sock;
|
|
my $now;
|
|
my $files_to_flush = [];
|
|
my $status;
|
|
|
|
if (!defined $opts{'begin'})
|
|
{
|
|
cluck ("begin is not defined");
|
|
return;
|
|
}
|
|
$begin = $opts{'begin'};
|
|
|
|
if (!defined $opts{'end'})
|
|
{
|
|
cluck ("end is not defined");
|
|
return;
|
|
}
|
|
$end = $opts{'end'};
|
|
|
|
if (!$opts{'addr'})
|
|
{
|
|
return (1);
|
|
}
|
|
|
|
$interval = $opts{'interval'} || 10;
|
|
|
|
if (ref ($all_files) eq 'HASH')
|
|
{
|
|
my @tmp = ($all_files);
|
|
$all_files = \@tmp;
|
|
}
|
|
|
|
$now = time ();
|
|
# Don't flush anything if the timespan is in the future.
|
|
if (($end > $now) && ($begin > $now))
|
|
{
|
|
return (1);
|
|
}
|
|
|
|
for (@$all_files)
|
|
{
|
|
my $file_orig = $_;
|
|
my $file_name = ident_to_filename ($file_orig);
|
|
my $file_copy = {};
|
|
my @statbuf;
|
|
my $mtime;
|
|
|
|
@statbuf = stat ($file_name);
|
|
if (!@statbuf)
|
|
{
|
|
next;
|
|
}
|
|
$mtime = $statbuf[9];
|
|
|
|
# Skip if file is fresh
|
|
if (($now - $mtime) <= $interval)
|
|
{
|
|
next;
|
|
}
|
|
# or $end is before $mtime
|
|
elsif (($end != 0) && (($end - $mtime) <= 0))
|
|
{
|
|
next;
|
|
}
|
|
|
|
$file_copy->{'host'} = $file_orig->{'hostname'};
|
|
$file_copy->{'plugin'} = $file_orig->{'plugin'};
|
|
if (exists $file_orig->{'plugin_instance'})
|
|
{
|
|
$file_copy->{'plugin_instance'} = $file_orig->{'plugin_instance'}
|
|
}
|
|
$file_copy->{'type'} = $file_orig->{'type'};
|
|
if (exists $file_orig->{'type_instance'})
|
|
{
|
|
$file_copy->{'type_instance'} = $file_orig->{'type_instance'}
|
|
}
|
|
|
|
push (@$files_to_flush, $file_copy);
|
|
} # for (@$all_files)
|
|
|
|
if (!@$files_to_flush)
|
|
{
|
|
return (1);
|
|
}
|
|
|
|
$sock = Collectd::Unixsock->new ($opts{'addr'});
|
|
if (!$sock)
|
|
{
|
|
return;
|
|
}
|
|
|
|
$status = $sock->flush (plugins => ['rrdtool'], identifier => $files_to_flush);
|
|
if (!$status)
|
|
{
|
|
cluck ("FLUSH failed: " . $sock->{'error'});
|
|
$sock->destroy ();
|
|
return;
|
|
}
|
|
|
|
$sock->destroy ();
|
|
return (1);
|
|
} # flush_files
|
|
|
|
# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
|