HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Perl script to check disk usage and report via email

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
scriptdiskperlemailusagereportviaandcheck

Problem

Due to lack of central configuration management I am avoiding any Perl modules which would need to be installed on each of a great many servers. Result is more code than would normally be required in order to format HTML and send email as well as make system calls which are less desirable.

```
#!/usr/bin/perl

use strict;
use warnings;
use Sys::Hostname;
use POSIX qw(uname);

my (%fsSize, %fsFree, %fsPct, %overrides);
my ($thresh, $fh);
my $repFile = "/tmp/chkDiskResults.txt";
my $send = 0;
my $hostname = hostname();
my @uname = uname();

# Determine the OS and set the 'df' command appropriately
my $df;
if ($uname[0] =~ 'AIX') {
$df = "df -tg";
} elsif ($uname[0] =~ 'Linux') {
$df = "df -h";
}

# Check for an override file loading it if it exists and running
# simple checks to ensure the values are valid.
my $overrideFile = "/etc/override";
if (-e $overrideFile) {
open($fh, ') {
my @split = split /\s+/, $line;
unless (!$split[1] || $split[1] !~ /^[0-9]+$/) {
$overrides{$split[0]} = $split[1];
}
}
close($fh);
}

# Execute the system 'df' command ignoring anything that isn't a
# real filesystem
# $cols[1] => Total space in GB
# $cols[3] => Free space in GB
# $cols[4] => Percent used column
# $cols[5] => Mounted on column
foreach my $line (qx[$df |grep -E -v "(Filesystem|proc|tmpfs)"]) {
my @cols = split /\s+/, $line;
chop($cols[1]);
chomp($cols[3]);
chop($cols[4]);

# set threshold based on disk size; A 1TB disk doesn't need
# to alert when 100GB are available
if ($cols[1] >= 800) {
$thresh = 98;
} elsif ($cols[1] = 400) {
$thresh = 96;
} elsif ($cols[1] = 200) {
$thresh = 94;
} elsif ($cols[1] = 100) {
$thresh = 92;
} else {
$thresh = 90;
}

$fsSize{$cols[5]} = $cols[1];# . "G";
$fsFree{$cols[5]} = $cols[3];# . "G";
$fsPct{$cols[5]} = $cols[4];
}

# Do the needful; override the thresholds if necessary; write
# offending filesystems to /tmp/chkDiskResults.txt as HTML
# since Outlook mang

Solution

-
If you have a machine with something like a dvd reader/writer and a disk inserted, the use % will always be 100%. This is actually more common than one might think if, for example, you have a virtual machine with VirtualBox and the guest additions CD is mounted.

-
On my own machine (Ubuntu 14.04), the output of df -h is

udev 3,9G 4,0K 3,9G 1% /dev
none 3,9G 147M 3,8G 4% /run/shm


Which will not be parsed correctly because of the , instead of the .

As for the code itself:

  • You're not using a proper temp file. I understand not wanting to use external modules, but File::Temp is a core module and it will be there unless your perl environment is broken.



  • You don't have even a single sub and this makes the code a lot less readable.



  • You have a double negation in unless (!$split[1] || $split[1] !~ /^[0-9]+$/) {. This is the same as if ($split[1] || $split[1] =~ /^[0-9]+$/) which I think is more readable (btw, unless is the same as if not, but it's not exactly the same as if ! because of precedence).



These lines:

my @cols = split /\s+/, $line;
chop($cols[1]);
chomp($cols[3]);
chop($cols[4]);


can be replaced by

my @cols = map { substr($_, 0, length($_)-1) || 0 } split /\s+/, $line;

This part:

if ($cols[1] >= 800) {
    $thresh = 98;
  } elsif ($cols[1] = 400) {
    $thresh = 96;
  } elsif ($cols[1] = 200) {
    $thresh = 94;
  } elsif ($cols[1] = 100) {
    $thresh = 92;
  } else {
    $thresh = 90;
  }


Is actually just:

if ($cols[1] >= 800) {
    $thresh = 98;
  } elsif ($cols[1] >= 400) {
    $thresh = 96;
  } elsif ($cols[1] >= 200) {
    $thresh = 94;
  } elsif ($cols[1] >= 100) {
    $thresh = 92;
  } else {
    $thresh = 90;
  }


  • There is no reason to have ` and ` in a separate print.



  • You're not differentiating thresholds by filesystem, you have only one even if the filesystems have different sizes.



  • I think it would be more readable if you used only one hash to store everything and distinguish by key.



This is part of the script how I would write it. It's not complete, because it's missing the override and the sending part, but I'm sure you can figure out how to do that.

#!/usr/bin/perl

use strict;
use warnings;

use File::Temp qw(tempfile);
use Sys::Hostname;
use POSIX qw(uname);

use Data::Dumper;

my %overrides;
my $send     = 0;

my $filename = write_report_file( get_fs_data() );
print Dumper $filename; # This is the file you can send

sub get_df_command {
    my $df;
    my @uname = uname();
    if ( $uname[0] =~ 'AIX' ) {
        $df = "df -tg";
    }
    elsif ( $uname[0] =~ 'Linux' ) {
        $df = "df -h";
    }

    return $df;
}

sub get_fs_data {
    my %fs_data;
    my $df = get_df_command();
    foreach my $line (qx[$df |grep -E -v "(Filesystem|proc|tmpfs)"]) {
        $line =~ s/,/\./g;
        my @cols = map { substr( $_, 0, length($_) - 1 ) || 0 } split(/\s+/, $line);

        my $thresh;
        if ( $cols[1] >= 800 ) {
            $thresh = 98;
        }
        elsif ( $cols[1] >= 400 ) {
            $thresh = 96;
        }
        elsif ( $cols[1] >= 200 ) {
            $thresh = 94;
        }
        elsif ( $cols[1] >= 100 ) {
            $thresh = 92;
        }
        else {
            $thresh = 90;
        }

        $fs_data{ $cols[5] } = {
            total        => $cols[1],
            free         => $cols[3],
            percent_used => $cols[4],
            threshold    => $thresh,
        };
    }

    return \%fs_data;
}

sub write_report_file {
    my ($data) = @_;

    my %fs_data = %{$data};
    my $hostname = hostname();
    my ( $rep_fh, $rep_filename ) = tempfile( UNLINK => 0 ) or die "Unable to open temp file: $!";

    print $rep_fh 
  
    Disk usage report for $hostname
    
      
        Filesystem
        Size
        Free
        Percent Used
      
EOF

    foreach my $filesystem ( keys(%fs_data) ) {
        my $thresh = $overrides{$filesystem} // $fs_data{$filesystem}->{threshold};

        if ( $fs_data{$filesystem}->{percent_used} >= $thresh ) {
            $send = 1;
            print $rep_fh 
        $filesystem
        $fs_data{$filesystem}->{total}G
        $fs_data{$filesystem}->{free}G
        $fs_data{$filesystem}->{percent_used}%
        
EOF
        }
    }

    print $rep_fh 
  

EOF

    close($rep_fh);
    return $rep_filename;
}

Code Snippets

my @cols = split /\s+/, $line;
chop($cols[1]);
chomp($cols[3]);
chop($cols[4]);
if ($cols[1] >= 800) {
    $thresh = 98;
  } elsif ($cols[1] < 800 && $cols[1] >= 400) {
    $thresh = 96;
  } elsif ($cols[1] < 400 && $cols[1] >= 200) {
    $thresh = 94;
  } elsif ($cols[1] < 200 && $cols[1] >= 100) {
    $thresh = 92;
  } else {
    $thresh = 90;
  }
if ($cols[1] >= 800) {
    $thresh = 98;
  } elsif ($cols[1] >= 400) {
    $thresh = 96;
  } elsif ($cols[1] >= 200) {
    $thresh = 94;
  } elsif ($cols[1] >= 100) {
    $thresh = 92;
  } else {
    $thresh = 90;
  }
#!/usr/bin/perl

use strict;
use warnings;

use File::Temp qw(tempfile);
use Sys::Hostname;
use POSIX qw(uname);

use Data::Dumper;

my %overrides;
my $send     = 0;

my $filename = write_report_file( get_fs_data() );
print Dumper $filename; # This is the file you can send

sub get_df_command {
    my $df;
    my @uname = uname();
    if ( $uname[0] =~ 'AIX' ) {
        $df = "df -tg";
    }
    elsif ( $uname[0] =~ 'Linux' ) {
        $df = "df -h";
    }

    return $df;
}

sub get_fs_data {
    my %fs_data;
    my $df = get_df_command();
    foreach my $line (qx[$df |grep -E -v "(Filesystem|proc|tmpfs)"]) {
        $line =~ s/,/\./g;
        my @cols = map { substr( $_, 0, length($_) - 1 ) || 0 } split(/\s+/, $line);

        my $thresh;
        if ( $cols[1] >= 800 ) {
            $thresh = 98;
        }
        elsif ( $cols[1] >= 400 ) {
            $thresh = 96;
        }
        elsif ( $cols[1] >= 200 ) {
            $thresh = 94;
        }
        elsif ( $cols[1] >= 100 ) {
            $thresh = 92;
        }
        else {
            $thresh = 90;
        }

        $fs_data{ $cols[5] } = {
            total        => $cols[1],
            free         => $cols[3],
            percent_used => $cols[4],
            threshold    => $thresh,
        };
    }

    return \%fs_data;
}

sub write_report_file {
    my ($data) = @_;

    my %fs_data = %{$data};
    my $hostname = hostname();
    my ( $rep_fh, $rep_filename ) = tempfile( UNLINK => 0 ) or die "Unable to open temp file: $!";

    print $rep_fh <<"EOF";
<html>
  <body>
    <h1>Disk usage report for $hostname</h1>
    <table width="500">
      <tr>
        <th align="left">Filesystem</th>
        <th>Size</th>
        <th>Free</th>
        <th>Percent Used</th>
      </tr>
EOF

    foreach my $filesystem ( keys(%fs_data) ) {
        my $thresh = $overrides{$filesystem} // $fs_data{$filesystem}->{threshold};

        if ( $fs_data{$filesystem}->{percent_used} >= $thresh ) {
            $send = 1;
            print $rep_fh <<"EOF";
        <tr>
        <td>$filesystem</td>
        <td align="center">$fs_data{$filesystem}->{total}G</td>
        <td align="center">$fs_data{$filesystem}->{free}G</td>
        <td align="center">$fs_data{$filesystem}->{percent_used}%</td>
        </tr>
EOF
        }
    }

    print $rep_fh <<"EOF";

    </table>
  </body>
</html>
EOF

    close($rep_fh);
    return $rep_filename;
}

Context

StackExchange Code Review Q#160384, answer score: 2

Revisions (0)

No revisions yet.