#!/usr/bin/perl -w # Copyright (C) 2003 Harold van Oostrom # $Id: setup_monitor.pl,v 1.1 2003/05/15 17:28:18 build Exp $ use strict; use lib "$ENV{HOME}/lib"; use PowerEdge::RAC qw(prompt); use File::Path (); use File::Spec qw(catdir); use FileHandle; use RRDs; my ($rac, $user, $password) = ('') x 3; print qq{ This script will setup an rrd database where sensor data will be stored, a cronjob that will collect the data and a webpage where graphs will be shown. It requires access to an existing RAC card. }; $rac = prompt('Enter the IP address of the RAC card that you want to '. "\n" . 'collect the sensor data from: ','192.168.0.120'); $user = prompt('Enter the RAC username : ', 'root'); $password = prompt('Enter the RAC password : ', 'calvin'); $rac ||= '192.168.0.120'; $user ||= 'root'; $password ||= 'calvin'; my $server = new PowerEdge::RAC( host => $rac, user => $user, password => $password, ); # See CPAN/FirstTime.pm for a way to extend this script to store its # config. Skip for now. my @previous_ids; print "\nTime according to the RAC: ", $server->get_gmt(); my $desc = desc_line($server); my ($host) = split / /, $desc; die ("Invalid hostname $host :$!\n") unless ($host =~ m/[a-z]\w+/i); $| = 1; print "Now detecting available sensors ..."; my @ids; push (@ids, "All sensors [0x0000000]"); my %sensors = %{ $server->read_sensors() }; die ("Empty sensorlist. Server turned off ? [$!]\n") unless (%sensors); print qq{ done. I have detected the following sensors eligible for data collection. From this list please select one or more of the sensors you want to monitor. Put their numbers on one line, separated by blanks. Example [] 1 2 4 7 9 If you want to change this aftwerwards, you can run this script again but then the sensor data collected thus far will be lost. }; # my $header = "Name sensor_type (current value) [ sensorid]\n"; for my $id (sort keys %sensors) { my %props = %{ $sensors{$id} }; my $val = $props{'VAL'}; my $name = $props{'NAME'}; my $units = $props{'UNITS'}; my $type = $props{'SENSOR_TYPE'}; next unless defined($val); next unless defined($name); next unless defined($units); next unless defined($type); next if ($val eq ''); next if ($units eq ''); if ($type =~ m/(Fan|Temperature|Voltage)/) { push(@ids, sprintf("%-16s%-11s ( %7s %-3s ) [%s]", $name, $type, $val, $units, $id)); }; }; my $no_previous_warn = "You must make a selection\n"; my @list = picklist(\@ids, "Enter the numbers ", '', ! @previous_ids, $no_previous_warn); if (grep (/0x0000000/, @list)) { @list = @ids; shift(@list); }; foreach (@list) { s/^.*\[(.*)\]$/$1/; } print 'Selected ', scalar(@list), ' sensors for monitoring.'."\n"; # create the rrd database to store the sensor data my $sensordatadir = File::Spec->catdir($ENV{HOME}, ".sensordata"); if (-d $sensordatadir) { print qq{ I see you already have a directory $sensordatadir Shall we use it as the directory to store sensordata ? }; } else { print qq{ First of all, I\'d like to create a directory for storing the rrd database(s). }; } my $ans; my $default = $sensordatadir; while ($ans = prompt("Directory to store sensor data?", $default)) { (warn "Please use the full pathname\n"), next if ($ans =~ m/^~/); eval { File::Path::mkpath($ans); }; # dies if it can't if ($@) { warn "Couldn't create directory $ans. Please retry.\n"; next; } if (-d $ans && -w _) { last; } else { warn "Couldn't find directory $ans or directory is not writable. Please retry.\n"; } } $sensordatadir = $ans; print qq{ We will create an rrd database, a directory to store the webpage, a script to be run from cron and a script to generate the webpage. All will be named using the same prefix. The default is to use the hostname as obtained from the RAC. This will allow multiple monitors to coexist. }; my $rrdb; $default = $host; while ($ans = prompt("Prefix for scriptnames and name of rrd database?", $default)) { $rrdb = File::Spec->catfile($sensordatadir, $ans.'.rrd'); if (-f $rrdb) { warn "There already seems to be an rrd database $rrdb Please use another prefix or remove the database and rerun this script.\n"; } else { last; }; } my $prefix = $ans; my $step = 300; my $heartbeat = 3 * $step; my $weeks = 4; # every $step seconds for $weeks weeks my $rows = int($weeks * 7 * 24 * 3600/$step); print qq{ The monitor data will be stored and the pages will be generated using a program named rrdtool. Where can I find it on your system ? }; $default = '/usr/bin/rrdtool'; while ($ans = prompt("Path to rrdtool executable ?", $default)) { $ans ||= $default; if (-x $ans) { last; } else { warn "Couldn't execute $ans Please retry. You need to install rrdtool first. You may find it here: http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/\n"; } }; my $rrdtool = $ans; my @DSs = (); my $LIST = ''; # we need this later for my $id (@list) { $LIST .= "'$id',\n"; my $name = $sensors{$id}->{'NAME'}; $name = make_dsname($name); push(@DSs, 'DS:'.$name.':GAUGE:'.$heartbeat.':U:U'); }; RRDs::create ($rrdb, "--step=$step", @DSs, "RRA:AVERAGE:0.5:1:$rows"); my $err = RRDs::error; die "ERROR while creating $rrdb: $err\n" if $err; print 'Succesfully created ', $rrdb, "\n"; print qq{ Now we need a directory where the generated webpage and images can be stored. This directory should be writable by the user running this script. Preferably it should be put somewhere in the documentroot of a webserver. }; my $outdir = File::Spec->catdir('/var/www/html/sensors/', $prefix); if (-d $outdir) { print qq{ I see you already have a directory $outdir Shall we use that ? }; } $default = $outdir; while ($ans = prompt("Directory to put the generated webpage?", $default)) { (warn "Please use the full pathname\n"), next if ($ans =~ m/^~/); eval { File::Path::mkpath($ans); }; # dies if it can't if ($@) { warn "Couldn't create directory $ans. Please retry.\n"; next; } if (-d $ans && -w _) { last; } else { warn "Couldn't find directory $ans or directory is not writable. Please retry.\n"; } } $outdir = $ans; my $imagedir = File::Spec->catdir($outdir, 'images'); eval { File::Path::mkpath($imagedir); }; # dies if it can't die ("Couldn't create directory $imagedir: $!\n") if ($@); print qq{ Finally we need a directory where we can put both the script that fetches the sensordata from the RAC and the script used to generate the webpages. Preferably this directory should be in your PATH. }; my $bindir = File::Spec->catdir($ENV{HOME}, "bin"); if (-d $bindir) { print qq{ I see you already have a directory $bindir Shall we use it as to store the script that generates the webpage ? }; } else { print qq{ First of all, I\'d like to create this directory. }; } $default = $bindir; while ($ans = prompt("Directory to store the page generation script?", $default)) { (warn "Please use the full pathname\n"), next if ($ans =~ m/^~/); eval { File::Path::mkpath($ans); }; # dies if it can't if ($@) { warn "Couldn't create directory $ans. Please retry.\n"; next; } if (-d $ans && -w _) { last; } else { warn "Couldn't find directory $ans or directory is not writable. Please retry.\n"; } } $bindir = $ans; my $scriptname = $prefix.'_createpage.cgi'; # Create the data collection script, to be run from cron my $txt = << 'END_OF_SCRIPT'; #!/usr/bin/perl -w use strict; # use lib "$ENV{HOME}/lib"; use PowerEdge::RAC; use RRDs; my $rac = 'RACSTR'; my $user = 'USER'; my $password = 'PASSWORD'; my $rrdb = 'RRDB'; my $outdir = 'OUTDIR'; my @sensorlist = ( 'LIST', ); my $server = new PowerEdge::RAC( host => $rac, user => $user, password => $password, ); my $data = 'N'; my %sensors = %{ $server->read_sensors(@sensorlist) }; my $err; my $missingvalue = 0; for my $sensorid (@sensorlist) { my $props = $sensors{$sensorid}; unless (defined ($props)) { $err .= sprintf("[%10s] undefined \n", $sensorid); $missingvalue = 1; next; }; my $name = $props->{'NAME'}; my $val = $props->{'VAL'}; for my $k (keys %$props) { my $v = $props->{$k} || ''; $err .= sprintf("[%10s] %20s => %16s\n", $sensorid, $k, $v); }; if (defined($val)) { $data .= ':' . $val; } else { $missingvalue = 1; }; }; die ("Missing value(s)\n$err\n") if ($missingvalue); RRDs::update ($rrdb, $data); $err = RRDs::error; die "ERROR while updating $rrdb: $err\n" if $err; #system("/usr/bin/rrdtool update $rrdb $data"); #print $data, "\n"; system("BINDIR/d_RRDCGI < /dev/null | egrep -v '(offline|Content)' > $outdir/day.html"); system("BINDIR/w_RRDCGI < /dev/null | egrep -v '(offline|Content)' > $outdir/week.html"); system("BINDIR/m_RRDCGI < /dev/null | egrep -v '(offline|Content)' > $outdir/month.html"); END_OF_SCRIPT $txt =~ s/RACSTR/$rac/sg; $txt =~ s/USER/$user/sg; $txt =~ s/PASSWORD/$password/sg; $txt =~ s/RRDB/$rrdb/sg; $txt =~ s{OUTDIR}{$outdir}sg; $txt =~ s{BINDIR}{$bindir}sg; $txt =~ s{RRDCGI}{$scriptname}sg; $txt =~ s/'LIST',/$LIST/sg; my $cronscript = File::Spec->catfile($bindir, $prefix.'_getdata.pl'); my $fh = new FileHandle("$cronscript", 'w'); die ("Could not open $cronscript for writing: $! \n") if (!defined $fh); print $fh $txt; $fh->close; chmod 0700, $cronscript or die("Could not set permissions for $cronscript: $!\n"); print 'Succesfully created ', $cronscript, "\n"; print 'Please be aware that the RAC password is stored in plaintext in this file.',"\n"; # Create the rrdcgi scripts that generates the pages my @periods = ( 'day', 'week', 'month' ); for my $period (@periods) { my $p = substr $period, 0, 1; my $pagehead = << 'END_OF_SCRIPT'; #!/usr/bin/rrdcgi Sensor readings for host: DESCRIPTION

Sensor readings for the past PERIOD

See also readings for the past OTHERS

END_OF_SCRIPT my ($first, $second) = grep { $_ !~ /$period/ } @periods; my $others = qq{ $first or $second }; $pagehead =~ s/DESCRIPTION/$desc/sg; $pagehead =~ s/PERIOD/$period/sg; $pagehead =~ s/OTHERS/$others/sg; my $std_rowhead = << 'END_OF_SCRIPT'; END_OF_SCRIPT my $std_element = << 'END_OF_SCRIPT'; END_OF_SCRIPT #XTAGS #my %xtags = ( # day => q{ -x HOUR:1:HOUR:3:HOUR:6:0:'%b %d %H:00' }, # week => q{ -x HOUR:6:DAY:1:DAY:1:86400:'%a %d' }; # month => q{ -x DAY:1:DAY:7:DAY:7:86400:'%a %d' }; #); sub shift2(\@) { return splice(@{$_[0]}, 0, 2); }; my $block = $pagehead; my @slist = @list; while (my ($left, $right) = shift2(@slist)) { my $tablerow = '' . "\n"; my ($lefttitle, $righttitle) = '' x 2; for my $id ($left, $right) { next unless defined($id); my %props = %{ $sensors{$id} }; my $name = $props{'NAME'}; my $units = $props{'UNITS'}; my $type = $props{'SENSOR_TYPE'}; my $dsname = make_dsname($name); my $imagename = $dsname. '_' . $p; $type =~ s/Fan/Speed/; my $scalelines; if ($type =~ m/Speed/) { $scalelines = ' --units-exponent 0' . "\n" . ' --alt-autoscale'; } else { $scalelines = ' --alt-y-grid'; }; my $title = $name . ' ' . $type; $lefttitle = $title if ($id eq $left); if (defined($right)) { $righttitle = $title if ($id eq $right); }; my $element = $std_element; $element =~ s/PERIOD/$period/sg; $element =~ s/UNITS/$units/sg; $element =~ s/TITLE/$title/sg; $element =~ s/DSNAME/$dsname/sg; $element =~ s/IMAGENAME/$imagename/sg; $element =~ s/OUTDIR/$outdir/sg; $element =~ s/RRDBPATH/$rrdb/sg; $element =~ s/SCALELINES/$scalelines/sg; $tablerow .= $element; } my $rowhead = $std_rowhead; $rowhead =~ s/LEFTTITLE/$lefttitle/sg; $righttitle ||= ''; $rowhead =~ s/RIGHTTITLE/$righttitle/sg; $block .= $rowhead . $tablerow . '' . "\n"; }; my $pageend = << 'END_OF_SCRIPT';

LEFTTITLE

RIGHTTITLE

' -a PNG -h 100 -w 400 --lazy -s -1PERIOD -v "UNITS" -t "TITLE" SCALELINES DEF:temp=RRDBPATH:DSNAME:AVERAGE LINE2:temp#FF00FF>

Last change:

Host monitored with PowerEdge::RAC

Page created with rrdtool END_OF_SCRIPT $block .= $pageend; my $p_scriptname = $p . '_' . $scriptname; $p_scriptname = File::Spec->catfile($bindir, $p_scriptname); $fh = new FileHandle($p_scriptname, 'w'); die ("Could not open $p_scriptname for writing: $! \n") if (!defined $fh); print $fh $block; $fh->close; chmod 0755, $p_scriptname or die("Could not set execute permissions for $p_scriptname: $!\n"); print 'Succesfully created ', $p_scriptname,"\n"; }; # add the cronjob # todo read from crontab -l, remove std lines, add our line # write with crontab - my $perhour = int($step/60); print qq{ To complete the setup please add the following line to your crontab */$perhour * * * * $cronscript }; print "\n", 'That is all, thank you. Enjoy your graphs !', "\n"; exit 0; sub picklist { my ($items, $prompt, $default, $require_nonempty, $empty_warning) = @_; $default ||= ''; my ($item, $i); for $item (@$items) { printf "(%2d) %s\n", ++$i, $item; } my @nums; while (1) { my $num = prompt($prompt, $default); @nums = split (' ', $num); (warn "Invalid items entered, try again\n"), next if grep (/\D/ || $_ < 1 || $_ > $i, @nums); if ($require_nonempty) { (warn "$empty_warning\n"), next unless @nums; } last; } print "\n"; for (@nums) { $_-- }; @{$items}[@nums]; }; sub desc_line { my $server = shift; my $si = $server->sysinfo(); my ($host) = split /\./, $si->{'Host Name'}; $host ||= 'unknown'; my $bios = $si->{'BIOS Version'}; my $model = $si->{'System Model'}; $model =~ s/PowerEdge //; my $os = $si->{'OS Name'}; $os ||= '?'; #my $asset = $si->{'Asset Tag'}; #$asset ||= ''; my $ip = $si->{'Current IP Address'}; my $hw = $si->{'Hardware Version'}; my $fw = $si->{'Firmware Version'}; $fw =~ s/\(.*\)//; $fw =~ s/\s//g; $host.' M['.$model.'] B['.$bios.'] O['.$os.'] F['.$fw.'] H['.$hw.'] I['.$ip.']'; }; sub make_dsname { my $s = shift; $s =~ s/\s/_/g; $s = uc($s); $s =~ s/\+/V/g; $s =~ s/\./_/g; $s =~ s/\//_/g; $s; };