Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

The purpose of the script is to take vmstat output and pull out the free memory and free swap and if the average falls below a set limit send out an e-mail. Is there a cleaner more Perly way to do this with less lines?
#!/usr/bin/perl -w # # Version: 1.1 # Purpose: Script determines the available free memory and swap o +n the system # and if the free memory or swap drops below a set thres +hold send e-mail. # use lib '/export/home/t31zrwf/perl_modules'; use MIME::Lite; # Limit Swap is 5GB and Memory is 3GB. $swaplim=5242880; $memlim=3145728; # Grep out lines not beginging with numbers and pump into an array. @vmstat = `vmstat 1 10 | grep -v '^ procs'| grep -v '^ r'`; # Push freeswap and freemem into separate arrays. foreach $x (@vmstat) { chomp $x; ($pos1,$pos2,$pos3,$pos4,$pos5,$pos6) = split / /, $x; push @mylist, "$pos5\n"; push @mylist1, "$pos6\n"; } # Remove first element of array because first line of vmstat gives information since boot. shift @mylist; foreach $z (@mylist) { chomp $z; $tswap += $z; } # Get average free swap. $avgswap = ($tswap/($#mylist+1)); # Remove first element of array because first line of vmstat gives information since boot. shift @mylist1; foreach $z (@mylist1) { chomp $z; $tmem += $z; } # Get average free memory. $avgmem = ($tmem/($#mylist1+1)); open(FILE,"> /tmp/error.out") || die "Can't open:$!\n"; if ($avgswap <= $swaplim) { print FILE "WARNING!!!!WARNING!!!!WARNING!!!!\n"; printf FILE "The average free swap is: %d\n", $avgswap; print FILE "The average free swap should be: $swaplim\n"; } if ($avgmem <= $memlim) { print FILE "WARNING!!!!WARNING!!!!WARNING!!!!\n"; printf FILE "The average freemem is: %d\n", $avgmem; print FILE "The average free memory should be: $memlim\n"; } close(FILE); # Set system name. chomp($sys_name=`uname -n`); $msg = new MIME::Lite; $msg->build( To => 'email_address', Subject => "Test from $sys_name", Type => 'text', Path => '/tmp/error.out', ); # Send e-mail or DIE! $msg->send('sendmail') || die "Can't open:$!\n"; # Remove temp file unlink "/tmp/error.out";

Replies are listed 'Best First'.
Re: vmstat threshold script
by gellyfish (Monsignor) on Jun 23, 2003 at 16:52 UTC

    On linux I would be inclined to read from /proc/meminfo rather than spawning an external program - infact I thought I had started to write a module to do just that but can't find it anywhere

    Update: I remember why I never did it now - because the already is one on cpan - Linux::Meminfo ;-)

    /J\
    
Re: vmstat threshold script
by ant9000 (Monk) on Jun 23, 2003 at 17:25 UTC
    On Linux I'd rather use /proc/meminfo: it's fast and you obtain the total amount of mem/swap, too. A simplistic solution could be this:
    use strict; use warnings; my $meminfo='/proc/meminfo'; my %Limits=('mem'=>0.99, 'swap'=>0.60); my %Info=(); open(MEM,"<$meminfo") or die "Can't read $meminfo: $!"; while(defined($_=<MEM>)){ if(/^(Mem|Swap):\s*/){ my $which=lc($1); my ($total,$used)=split(/\s/,$'); my $pct=$total?($used/$total):0; my $test=($pct ge $Limits{$which}?"WARN":"OK"); $Info{$which}=[$used, $total, $pct, $test]; } } close(MEM); #Quick and dirty way to print the %Info hash in a readable form use Data::Dumper; print Dumper(\%Info);

    This way, the parsing code is pretty easy; after that, you can use %Info for anything you need.
      Thanks! Although this is for a large Sun server running Solaris 8
Re: vmstat threshold script
by jkenneth (Pilgrim) on Jun 24, 2003 at 15:11 UTC
    Another trick that I am fond of would be to do the actual vmstat within an open:
    open (VMSTAT, "/usr/bin/vmstat 1 10|"); while (<VMSTAT>) { next if ($_ !~ /^ \d+/); (undef,undef,undef,$pos5,$pos6) = split; # CountZero++ push @mylist1, $pos5; push @mylist2, $pos6; }
    Also, I'm not familiar with MIME::Lite but I'd look at creating the message without actually creating a temporary file since you just get rid of it anyhow.

    JK
Re: vmstat threshold script
by CountZero (Bishop) on Jun 23, 2003 at 21:40 UTC

    Rather than shifting @mylist and @mylist1, you can immediately shift @vmstat to get rid of the first record: saves you one shift operation and one loop through @vmstat.

    It is not necessary to chomp $z when looping through @mylist and @mylist1 if you do not add the "\n" to the result of the split when looping through @vmstat: saves you two chomp operations.

    You can even leave out the first chomp $x, as your split / /, $x will drop the EOL-character already.

    Speaking of the split / /, $x: as you only need the fifth and sixth element of each record, why not saying:

    (undef, undef, undef, undef, my $pos5, my $pos6) = split / /, $x

    And finally (at the risk of becoming obfuscated):
    foreach (@vmstat) { # assigns to $_ (undef,undef,undef,undef,$pos5,$pos6) = split; # splits $_ on + whitespace push @mylist, $pos5; push @mylist1, $pos6; }

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law