#!/usr/bin/perl -wT # Copyright (C) 2009 Espen Braastad # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # This is a script to collect munin graphs into one html page # from multiple munin installations. # # Based on Ingvar Hagelunds munin collect script # # # $log$ # 2010-08-12 espen # Finally added proper support for munin 1.4. # # # Schedule to run in crontab, i.e. once pr. hour: # 0 */1 * * * user /path/to/munincollector-ng/munincollector-ng -c /path/to/munincollector-ng.conf # use strict; use warnings; use LWP::UserAgent; use File::Path; use File::Copy; use Getopt::Std; my $version="0.8"; my $debug=0; my $opt_string = 'hc:'; my %opt; getopts( "$opt_string", \%opt ) or usage(); usage() if $opt{h}; my $config=""; if(-r "/etc/munin/munincollector-ng.conf"){; $config="/etc/munin/munincollector-ng.conf"; } if(defined($opt{c})){ if(-r "$opt{c}"){ $config=$opt{c}; } } if(! -r $config){ print "Unable to read configuration file.\n"; usage(); exit; } my %config=&read_config($config); if(!defined($config{'plugin'})){ print "graph.plugin not set in the configuration file.\n"; exit; } my $plugin=$config{'plugin'}; if(!defined($config{'type'})){ print "graph.type not set in the configuration file.\n"; exit; } my @graph_types=split(" ",$config{'type'}); if(!defined($config{'dir'})){ print "graph.dir not set in the configuration file.\n"; exit; } my $dir=$config{'dir'}; if(! -w ${dir}){ print "graph.dir ($dir) is not writable.\n"; exit; } my $log=undef; if(defined($config{'log'})){ $log=$config{'log'}; if(-e $log){ if(! -w $log){ print $log . " is not writable (graph.log)\n"; exit; } } else { open (FILE, '>' . $log) or die "Unable to create $log"; close (FILE); } } my %urls=&read_urls($config); # Making sure the logo and stylesheet is present in $dir if(-r "/etc/munincollector-ng/logo.png"){ if(! -r $dir . "/logo.png"){ copy("/etc/munincollector-ng/logo.png",$dir); } } if(-r "/etc/munincollector-ng/style.css"){ if(! -r $dir . "/style.css"){ copy("/etc/munincollector-ng/style.css",$dir); } } my %res=(); foreach my $domain ( keys %urls) { $debug && print "Domain: " . $domain . "\n"; if(defined($urls{$domain}{'url'})){ my $url=$urls{$domain}{'url'}; my $username=""; my $password=""; my $realm=""; my $netloc=""; if(defined($urls{$domain}{'username'}) && defined($urls{$domain}{'password'}) && defined($urls{$domain}{'netloc'}) && defined($urls{$domain}{'realm'})) { $username=$urls{$domain}{'username'}; $password=$urls{$domain}{'password'}; $realm=$urls{$domain}{'realm'}; $netloc=$urls{$domain}{'netloc'}; #$debug && print " (Login)\n"; } else { #$debug && print " (No login)\n"; } $res{$domain}{'url'}=$url; $res{$domain}{'data'}=&parse_urls($url,$username,$password,$realm,$netloc,$domain); } else { dolog("Invalid entry in " . $config) if $log; } } my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time); my $generated=sprintf("%4d-%02d-%02d T %02d:%02d:%02d\n", $year+1900,$mon+1,$mday,$hour,$min,$sec); # Function to read and parse the websites containing the munin "front page" # Return a hash with URLs to the graphs sub parse_urls{ my $url = $_['0']; my $username = $_['1']; my $password = $_['2']; my $realm = $_['3']; my $netloc = $_['4']; my $domain = $_['5']; my $ua = LWP::UserAgent->new; $ua->credentials($netloc,$realm,$username,$password); my $response = $ua->get($url); my %data=(); if ($response->is_success) { dolog("> Fetched successfully: $url") if $log; my @lines=split(/\n/,$response->content); my $munin_version="Not found"; foreach my $line (@lines){ if($line =~ m/\s*This page was generated by.*version 1\.4.*/){ $munin_version="1.4"; } elsif($line =~ m/\s*This page was generated by.*version 1\.2.*/){ $munin_version="1.2"; } } $debug && print " Munin version: " . $munin_version . "\n"; foreach my $line (@lines){ # Get the hosts and groups monitored by this munin. # Look for the following lines: #
  • foo.bar.no :: if($munin_version == "1.2"){ if($line =~ m/\s*
  • Failed to fetch ' . $graph . '

    '); $debug && print " Failed to fetch graph.\n"; } $debug && print "\n"; } } } elsif ($munin_version == "1.4"){ if($line =~ m/\s*
  • Failed to fetch ' . $graph . '

    '); $debug && print " Failed to fetch graph.\n"; } $debug && print "\n"; } } } } } else { #TODO dolog("Failed to parse: " . $url . " -> " . $response->status_line) if $log; } return \%data; } #use Data::Dumper; #print Dumper(%res); #exit; # Empty the html file open (FILE, '>' . $dir . '/index.html'); close (FILE); &html(' Munin :: collector-ng

    Munin collector-ng

    '); &html('

    Collecting the '); foreach my $graph_type (@graph_types) { &html(' ' . $graph_type . ' '); } &html(' graphs from the ' . $plugin . ' plugin.

    '); &html('

    Overview

    '); # Creating a short index &html('
      '); for my $domain ( sort keys %res ){ # Looping through the munin instances (domains) &html('
    • ' . $domain . ' :: ['); my $count=0; for my $subkey ( sort keys %{$res{$domain}{'data'}} ){ # Looping through the groups my $group=$subkey; &html(' ' . $group . ' '); $count+=keys(%{$res{$domain}{'data'}{$group}}); } &html(' ]'); &html(' ' . $count); if($count != 1){ &html(' hosts'); } else { &html(' host'); } } &html('
    '); &html('
    '); &html('

    Graphs

    '); # Generating the list of graphs &html('
      '); for my $key ( sort keys %res ){ # Looping through the munin instances (domains) my $domain=$key; my $url=$res{$domain}{'url'}; &html('
    • ' . $domain . '
    • '); &html('
        '); for my $subkey ( sort keys %{$res{$key}{'data'}} ){ # Looping through the groups my $group=$subkey; &html('
      • ' . $group . '
      • '); &html(' '); } &html('
      '); } &html('
    '); &html('
    This page was generated by munin collector-ng version ' . $version . ' at ' . $generated . '
    '); # Function to read the urls from the configuration file sub read_urls{ my $file=$_[0]; my %urls=(); if(-r $file){ open(FILE,'<'.$file); while () { my $line=$_; chomp($line); # Skipping comments if($line =~ m/^[^#]/ && $line !~ m/^\s*$/ && $line !~ m/^graph\./){ #$debug && print "[" . $line . "]\n"; if($line =~ m/^([^\.]+)\.([^\s]+)\s+(.*)/){ $urls{$1}{$2}=$3; } } } close (FILE); } return %urls; } # Function to read the settings from the configuration file sub read_config{ my $file=$_[0]; my %config=(); if(-r $file){ open(FILE,'<'.$file); while () { my $line=$_; chomp($line); # Skipping comments if($line =~ m/^[^#]/ && $line !~ m/^\s*$/){ #$debug && print "[" . $line . "]\n"; if($line =~ m/^graph\.([^\s]+)\s+(.*)/){ if(length($2)>0){ $config{$1}=$2; } } } } close (FILE); } return %config; } sub fetch_image{ my $url = $_['0']; my $username = $_['1']; my $password = $_['2']; my $realm = $_['3']; my $netloc = $_['4']; my $dest = $_['5']; my $status=1; my $ua = LWP::UserAgent->new; $ua->credentials($netloc,$realm,$username,$password); my $request = new HTTP::Request('GET',$url); my $response = $ua->request($request,$dest); if($response->is_success){ #dolog("--> Downloaded successfully: " . $url . " -> " . $dest) if $log; $status=0; } else { dolog("--> Failed to download: " . $url . " -> " . $response->status_line) if $log; } return $status; } sub html{ my $text=$_['0']; open (FILE, '>>' . $dir . '/index.html'); print FILE $text . "\n"; close (FILE); } sub dolog{ my $text=$_['0']; if($log){ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time); my $generated=sprintf("%4d-%02d-%02d T %02d:%02d:%02d\n", $year+1900,$mon+1,$mday,$hour,$min,$sec); chomp($generated); open (FILE, '>>' . $log); print FILE $generated . " " .$text . "\n"; close (FILE); } } sub usage { print STDERR << "EOF"; Munin collector-ng Options: -h - This help screen -c - Specify an alternative configuration file to use. If this option is not used, munincollector-ng will look for /etc/munin/munincollector-ng.conf EOF exit; }