#!/usr/bin/perl use strict; use warnings; use Getopt::Long ('GetOptions'); use Data::Dumper (); use File::Basename ('dirname'); our $InDir = '/var/lib/collectd'; our $OutDir = '/tmp/collectd-4'; our $Hostname = 'localhost'; # Types: # +------------+----------------------+----+----+----+ # ! Subdir ! Type ! ti ! pi ! ex ! # +------------+----------------------+----+----+----+ # ! apache ! apache_bytes ! ! ! ! # ! apache ! apache_requests ! ! ! ! # ! apache ! apache_scoreboard ! x ! ! ! # ! battery ! charge ! ! x ! ! # ! apcups ! charge_percent ! ! ! ! # ! ! cpu ! x ! ! x ! # ! ! cpufreq ! x ! ! ! # ! battery ! current ! ! x ! ! # ! ntpd ! delay ! x ! ! ! # ! ! df ! x ! ! ! # ! ! disk ! x ! ! ! # ! dns ! dns_traffic ! ! ! ! # ! apple_se.. ! fanspeed ! x ! ! ! # ! mbmon ! fanspeed ! x ! ! ! # ! apcups ! frequency ! x ! ! ! # ! ntpd ! frequency_offset ! x ! ! ! # ! ! hddtemp ! x ! ! ! # ! interface ! if_errors ! ! x ! ! # ! interface ! if_packets ! ! x ! ! # ! ! lm_sensors ! ! ! ! # ! ! load ! ! ! ! # ! apcups ! load_percent ! ! ! ! # ! ! memory ! ! ! ! # ! ! multimeter ! ! ! ! # ! mysql ! mysql_commands ! x ! ! ! # ! mysql ! mysql_handler ! x ! ! ! # ! mysql ! mysql_qcache ! ! ! ! # ! mysql ! mysql_threads ! ! ! ! # ! ! nfs2_procedures ! x ! ! x ! # ! ! nfs3_procedures ! x ! ! x ! # ! dns ! opcode ! x ! ! ! # ! ! partition ! x ! ! ! # ! ! ping ! x ! ! ! # ! ! processes ! ! ! ! # ! processes ! ps_count ! x ! ! ! # ! processes ! ps_cputime ! x ! ! ! # ! processes ! ps_pagefaults ! x ! ! ! # ! processes ! ps_rss ! x ! ! ! # ! dns ! qtype ! x ! ! ! # ! dns ! rcode ! x ! ! ! # ! (*) ! sensors ! x ! ! ! # ! ! serial ! x ! ! ! # ! ! swap ! ! ! ! # ! ! tape ! x ! ! ! # ! apple_se.. ! temperature ! x ! ! ! # ! mbmon ! temperature ! x ! ! ! # ! ntpd ! time_dispersion ! x ! ! ! # ! ntpd ! time_offset ! x ! ! ! # ! apcups ! timeleft ! ! ! ! # ! ! traffic ! x ! ! ! ->rx,tx # ! vserver ! traffic ! x ! x ! ! ->rx.tx # ! ! users ! ! ! ! # ! apucups ! voltage ! x ! ! ! # ! battery ! voltage ! ! x ! ! # ! mbmon ! voltage ! x ! ! ! # ! vserver ! vs_memory ! ! x ! ! # ! vserver ! vs_processes ! ! x ! ! # ! vserver ! vs_threads ! ! x ! ! # ! ! wireless ! x ! ! ! # +------------+----------------------+----+----+----+ our %Subdirs = ( apache => 0, apcups => 0, apple_sensors => 0, battery => 1, dns => 0, interface => 1, mbmon => 0, mysql => 0, ntpd => 0, processes => 0, sensors => 1, vserver => 1 ); our %TypeTranslate = ( cpu => sub { $_ = shift; $_->{'plugin_instance'} = $_->{'type_instance'}; $_->{'type_instance'} = undef; $_; }, hddtemp => sub { $_ = shift; $_->{'plugin'} = 'hddtemp'; $_->{'type'} = 'temperature'; $_->{'type_instance'} = $_->{'type_instance'}; $_; }, if_errors => sub { $_ = shift; $_->{'type_instance'} = $_->{'plugin_instance'}; $_->{'plugin_instance'} = undef; $_; }, if_packets => sub { $_ = shift; $_->{'type_instance'} = $_->{'plugin_instance'}; $_->{'plugin_instance'} = undef; $_; }, nfs2_procedures => sub { $_ = shift; @$_{qw(plugin plugin_instance type type_instance)} = ('nfs', 'v2' . $_->{'type_instance'}, 'nfs_procedure', undef); $_; }, nfs3_procedures => sub { $_ = shift; @$_{qw(plugin plugin_instance type type_instance)} = ('nfs', 'v3' . $_->{'type_instance'}, 'nfs_procedure', undef); $_; }, partition => sub { $_ = shift; $_->{'plugin'} = 'disk'; $_; }, processes => sub { $_ = shift; $_->{'type'} = 'ps_state'; $_; }, traffic => sub { $_ = shift; $_->{'plugin'} =~ s/^traffic$/interface/; @$_{qw(plugin_instance type)} = (undef, 'if_octets'); $_; } ); our %TypeSplit = ( cpu => { from => [qw(user nice syst idle wait)], to => 'value', type_instance => [qw(user nice system idle wait)] }, memory => { from => [qw(used free buffers cached)], to => 'value', type_instance => [qw(used free buffered cached)] }, nfs3_procedures => { from => [qw(null getattr lookup access readlink read write create mkdir symlink mknod remove rmdir rename link readdir readdirplus fsstat fsinfo pathconf commit)], to => 'value' }, nfs2_procedures => { from => [qw(create fsstat getattr link lookup mkdir null read readdir readlink remove rename rmdir root setattr symlink wrcache write)], to => 'value' }, processes => { from => [qw(running sleeping zombies stopped paging blocked)], to => 'value' }, swap => { from => [qw(cached free used resv)], to => 'value', type_instance => [qw(cached free used reserved)] } ); our %TypeRename = ( traffic => { from => [qw(incoming outgoing)], to => [qw(rx tx)] }, vs_processes => { from => [qw(total)], to => [qw(value)] }, ); GetOptions ("indir|i=s" => \$InDir, "outdir|o=s" => \$OutDir, "hostname=s" => \$Hostname) or exit_usage (); die "No such directory: $InDir" if (!-d $InDir); our @Files = (); our %OutDirs = (); @Files = find_files (); for (@Files) { my $orig_filename = $_; my $orig = parse_file ($orig_filename); my $dest = translate_file ($orig); my $dest_filename = get_filename ($dest); my $dest_directory = dirname ($dest_filename); if (!exists ($OutDirs{$dest_directory})) { print "[ -d '$OutDir/$dest_directory' ] || mkdir -p '$OutDir/$dest_directory'\n"; $OutDirs{$dest_directory} = 1; } if (($orig->{'type'} eq 'disk') || ($orig->{'type'} eq 'partition')) { special_disk ($orig_filename, $orig, $dest_filename, $dest); } elsif (exists ($TypeSplit{$orig->{'type'}})) { my $src_dses = $TypeSplit{$orig->{'type'}}->{'from'}; my $dst_ds = $TypeSplit{$orig->{'type'}}->{'to'}; my $type_instances = exists ($TypeSplit{$orig->{'type'}}->{'type_instance'}) ? $TypeSplit{$orig->{'type'}}->{'type_instance'} : $TypeSplit{$orig->{'type'}}->{'from'}; for (my $i = 0; $i < @$src_dses; $i++) { my $src_ds = $src_dses->[$i]; $dest->{'type_instance'} = $type_instances->[$i]; $dest_filename = get_filename ($dest); print "./rrd_filter.px -i '$InDir/$orig_filename' -m '${src_ds}:${dst_ds}' -o '$OutDir/$dest_filename'\n"; } } else { print "cp '$InDir/$orig_filename' '$OutDir/$dest_filename'\n"; } if (exists ($TypeRename{$orig->{'type'}})) { my $src_dses = $TypeRename{$orig->{'type'}}->{'from'}; my $dst_dses = $TypeRename{$orig->{'type'}}->{'to'}; print "rrdtool tune '$OutDir/$dest_filename'"; for (my $i = 0; $i < @$src_dses; $i++) { print " --data-source-rename " . $src_dses->[$i] . ':' . $dst_dses->[$i]; } print "\n"; } } exit (0); sub translate_file { my $orig = shift; my $dest = {}; %$dest = %$orig; if (defined ($TypeTranslate{$orig->{'type'}})) { $TypeTranslate{$orig->{'type'}}->($dest); } return ($dest); } # translate_file sub get_filename { my $args = shift; my $filename = $args->{'host'} . '/' . $args->{'plugin'} . (defined ($args->{'plugin_instance'}) ? '-'.$args->{'plugin_instance'} : '') . '/' . $args->{'type'} . (defined ($args->{'type_instance'}) ? '-'.$args->{'type_instance'} : '') . '.rrd'; return ($filename); } sub parse_file { my $fullname = shift; my @parts = split ('/', $fullname); my $filename; my $host; my $plugin; my $plugin_instance; my $type; my $type_instance; $filename = pop (@parts); if ($filename =~ m/^([^-]+)(?:-(.*))?\.rrd$/) { $type = $1; $type_instance = $2; } else { return; } if (@parts) { my $dirname = pop (@parts); my $regex_str = join ('|', keys (%Subdirs)); if ($dirname =~ m/^($regex_str)(?:-(.*))?$/) { $plugin = $1; $plugin_instance = $2; } else { push (@parts, $dirname); } } if (!$plugin) { $plugin = $type; } if (@parts) { $host = pop (@parts); } else { $host = $Hostname; } return ({ host => $host, plugin => $plugin, plugin_instance => $plugin_instance, type => $type, type_instance => $type_instance }); } # parse_file sub find_files { my $reldir = @_ ? shift : ''; my $absdir = $InDir . ($reldir ? "/$reldir" : ''); my $dh; my @files = (); my @dirs = (); opendir ($dh, $absdir) or die ("opendir ($absdir): $!"); while (my $file = readdir ($dh)) { next if ($file =~ m/^\./); next if (-l "$absdir/$file"); if (-d "$absdir/$file") { push (@dirs, ($reldir ? "$reldir/" : '') . $file); } elsif ($file =~ m/\.rrd$/) { push (@files, ($reldir ? "$reldir/" : '') . $file); } } closedir ($dh); for (my $i = 0; $i < @dirs; $i++) { push (@files, find_files ($dirs[$i])); } return (@files); } # find_files {my $cache; sub _special_disk_instance { my $orig_instance = shift; if (!defined ($cache)) { my $fh; open ($fh, "< /proc/diskstats") or die ("open (/proc/diststats): $!"); $cache = {}; while (my $line = <$fh>) { chomp ($line); my @fields = split (' ', $line); $cache->{$fields[0] . '-' . $fields[1]} = $fields[2]; } close ($fh); } return (defined ($cache->{$orig_instance}) ? $cache->{$orig_instance} : $orig_instance); }} sub special_disk { my $orig_filename = shift; my $orig = shift; my $dest_filename = shift; my $dest = shift; my $dest_directory; $dest->{'type_instance'} = undef; $dest->{'plugin_instance'} = _special_disk_instance ($orig->{'type_instance'}); if ($dest->{'plugin_instance'} eq $orig->{'type_instance'}) { print qq(echo "You may need to rename these files" >&2\n); } $dest->{'type'} = 'disk_merged'; $dest_filename = get_filename ($dest); $dest_directory = dirname ($dest_filename); if (!exists ($OutDirs{$dest_directory})) { print "[ -d '$OutDir/$dest_directory' ] || mkdir -p '$OutDir/$dest_directory'\n"; $OutDirs{$dest_directory} = 1; } print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rmerged:read' -m 'wmerged:write' -o '$OutDir/$dest_filename'\n"; $dest->{'type'} = 'disk_octets'; $dest_filename = get_filename ($dest); print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rbytes:read' -m 'wbytes:write' -o '$OutDir/$dest_filename'\n"; $dest->{'type'} = 'disk_ops'; $dest_filename = get_filename ($dest); print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rcount:read' -m 'wcount:write' -o '$OutDir/$dest_filename'\n"; $dest->{'type'} = 'disk_time'; $dest_filename = get_filename ($dest); print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rtime:read' -m 'wtime:write' -o '$OutDir/$dest_filename'\n"; } sub exit_usage { print <