#!/usr/bin/perl -w

################################################
#
# Checks the dev-stat (only for status).
#   - warning state only for to much waiting IOs
#   - unknown is for unknown device
#
# Thomas Sesselmann <t.sesselmann@dkfz.de> 2010
#
# 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, either version 3 of the License, or
# (at your option) any later version.
#
# 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, see <http://www.gnu.org/licenses/>.
#
#
# Changelog:
# v0.9  2010.10.22 (ts)
#
####


use strict;
use Getopt::Mixed;

my %ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4);


our $opt_h = 0;
our $opt_w = 100;   # default warning level
our $opt_c = 1000;  # default critical level
our $opt_m = 0;     # no multipath is default
our $opt_sector_size = 512;
our $opt_proc_file = "/proc/diskstats";
our $opt_mapper = 0;# /dev/mapper/* name
our $opt_v = 0;     # full perfdata


my %devices = ();


sub start_stuff();
sub print_exit($$);
sub test_dev($);


## get params
start_stuff();

## multipath-device?
my $mdev = "";
my @devkeys = ();
if ( $opt_mapper ) {
  $mdev = @{ [keys(%devices)] }[0];
  if ( -b "/dev/mapper/$mdev" ) {
    my @s = stat("/dev/mapper/$mdev");
    my $hb = $s[6] >> 8;
    my $lb = $s[6] & 255;

    open FILE, "<$opt_proc_file" or print_exit("Error: Can't open file '$opt_proc_file': $!\n",$ERRORS{UNKOWN});
    while(<FILE>){
      if ( $_ =~ /^\s*$hb\s+$lb\s+(\S+)\s+/ ) { 
        #push @devkeys, $1;
        $devices{$1} = 0;
        delete $devices{$mdev};
        if ( $opt_m ) {
          if ( -d "/sys/block/$1/slaves" ) {
            opendir DIR1, "/sys/block/$1/slaves";
            foreach(readdir DIR1){
              next if (( $_ eq "." ) or ( $_ eq ".." ));
              push @devkeys, $_;
              $devices{$_} = 0;
            }
            closedir DIR1;
          }
        }
        last;
      }
    }
    close FILE;

  }
} 


## get data and output
my $return_txt='';
my $perfdata='';
my $status = test_dev(\%devices);
push @devkeys, sort keys %devices unless ( $opt_m );
foreach my $k ( @devkeys ) {
  $return_txt .= ", " if $return_txt ne "";
  $return_txt .= "$devices{$k}->{return_txt}";
  $perfdata   .= "$devices{$k}->{perfdata} ";
}
$return_txt = "DEV $mdev $status - (IOwait/Read/Write) $return_txt|$perfdata\n";
print_exit($return_txt,$ERRORS{$status});





#####################################################


### Start stuff
sub start_stuff()
{

  ## get options
  my $opt_def = "h help>h w=i c=i warning>w critical>c sector_size=i proc_file=s m multipath>m v full_perfdata>v mapper";
  my $usage  = " [Options]* [devices]*\n";
     $usage .= "Options:\n";
     $usage .= "     -h, --help              print this help message\n";
     $usage .= "     -w, --warning=<int>     Warning Level for waiting IOs (Default: '$opt_w\n";
     $usage .= "     -c, --critical=<int>    Critical Level for waiting IOs (Default: '$opt_c')\n";
     $usage .= "     --sector_size=<int>     Size of a Sector in Byte (Default: '$opt_sector_size')\n";
     $usage .= "     --proc_file=<string>    Proc file for reading (Default: '$opt_proc_file')\n";
     $usage .= "     -m, --multipath         The device is a multipath-Alias (Only _ONE_ device allowd!)\n";
     $usage .= "     --mapper                Get Name from /dev/mapper/* '$opt_mapper')\n";
     $usage .= "     -v, --full_perfdata     Get all performance data (Default: '$opt_v')\n";
     $usage .= "Devices: List of devices such as sda sdb sdc, (Default: All)\n";

  Getopt::Mixed::init( $opt_def );
  Getopt::Mixed::getOptions();

  ## usage:
  $opt_h=1 if $opt_w > $opt_c;
  $opt_mapper=1 if $opt_m;
  #$opt_h=1 if ( scalar @ARGV > 0 );
  
  foreach ( @ARGV ) { $devices{$_} = 0; }
  if (( $opt_m ) and ( keys %devices != 1 )) { $opt_h = 1; }

  Getopt::Mixed::abortMsg("$usage") if $opt_h;

  $devices{all}=0 if keys %devices == 0;

}


### Print Message and Exit with result-code
sub print_exit($$) 
{
  my $msg = shift;
  my $return_code = shift;

  print "$msg";
  exit $return_code;
}


### Test Dev file(s) and print result
sub test_dev($)
{
  my $devices = %{ (shift) };

  my $status = 'OK';

  open FILE,"<$opt_proc_file" or die "Error: Can't open file '$opt_proc_file': $!\n\n";
  while ( <FILE> ) {
    chomp $_;
    if (( $_ =~ /(^|\s)([a-z][\w\/-]*)((\s+\d+){11})$/i )or( $_ =~ /(^|\s)([a-z]+\/c\dd\d)\s+(.*)$/i )){
      my $key = lc $2;
      my $val = $3;
      if( exists $devices{$key} ) { # explizit
        $devices{$key}=$val; 
      }
      if( exists $devices{all} ) {
        #blacklist
        if ( $key =~ /^(ram|loop|dm-|sr|fd)/ ) { next; }
        #whitelist
        #if ( $key =~ /^[sh]d[a-z]/ ) { $devices{$key}=$val; }
        #default
        $devices{$key}=$val;
      }
    }
  }
  close FILE;

  delete $devices{all} if ( exists $devices{all} );

  foreach my $k ( keys %devices ) {

    if ( $devices{$k} eq "0" ) {
      $devices{$k} = { 
        return_txt=>"$k=(-/-)",
        perfdata=>'',
      };
      $status = 'UNKNOWN'; 
      next; 
    }

    my ($r,$r_merged,$r_sectors,$r_time,
        $w,$w_merged,$w_sectors,$w_time,
        $io_wait,$io_time,$io_weighted  ) = split " ",$devices{$k};
    #/lib/modules/`uname -r`/source/Documentation/iostats.txt
    # Field  1 -- # of reads issued
    #     This is the total number of reads completed successfully.
    # Field  2 -- # of reads merged, field 6 -- # of writes merged
    #     Reads and writes which are adjacent to each other may be merged for
    #     efficiency.  Thus two 4K reads may become one 8K read before it is
    #     ultimately handed to the disk, and so it will be counted (and queued)
    #     as only one I/O.  This field lets you know how often this was done.
    # Field  3 -- # of sectors read
    #     This is the total number of sectors read successfully.
    # Field  4 -- # of milliseconds spent reading
    #     This is the total number of milliseconds spent by all reads (as
    #     measured from __make_request() to end_that_request_last()).
    # Field  5 -- # of writes completed
    #     This is the total number of writes completed successfully.
    # Field  7 -- # of sectors written
    #     This is the total number of sectors written successfully.
    # Field  8 -- # of milliseconds spent writing
    #     This is the total number of milliseconds spent by all writes (as
    #     measured from __make_request() to end_that_request_last()).
    # Field  9 -- # of I/Os currently in progress
    #     The only field that should go to zero. Incremented as requests are
    #     given to appropriate struct request_queue and decremented as they finish.
    # Field 10 -- # of milliseconds spent doing I/Os
    #     This field is increases so long as field 9 is nonzero.
    # Field 11 -- weighted # of milliseconds spent doing I/Os
    #     This field is incremented at each I/O start, I/O completion, I/O
    #     merge, or read of these stats by the number of I/Os in progress
    #     (field 9) times the number of milliseconds spent doing I/O since the
    #     last update of this field.  This can provide an easy measure of both
    #     I/O completion time and the backlog that may be accumulating.
    
    my $r_bytes = $r_sectors * $opt_sector_size;
    my $w_bytes = $w_sectors * $opt_sector_size;

    #$io_wait
    #$r_time
    #$w_time

    #Perfdata: 'label'=value[UOM];[warn];[crit];[min];[max]

    $devices{$k} = {
      return_txt => '',
      perfdata   => 
                    "${k}_readB=${r_bytes}c; ${k}_writeB=${w_bytes}c; "
                   ."${k}_read_time=${r_time}c; ${k}_write_time=${w_time}c; "
                   ."${k}_io_wait=${io_wait}; ",
    };
    if ( $opt_v ) {
      $devices{$k}->{perfdata} = 
                    "${k}_readB=${r_bytes}c; ${k}_writeB=${w_bytes}c; "
                   ."${k}_read_time=${r_time}c; ${k}_write_time=${w_time}c; "
                   ."${k}_read=${r}c; ${k}_write=${w}c; "
                   ."${k}_read_merged=${r_merged}c; ${k}_write_merged=${w_merged}c; "
                   ."${k}_io_wait=${io_wait}; ${k}_io_time=${io_time}c; ${k}_io_weighted=${io_weighted}c;";
    }

    if (( $io_wait > $opt_w )and( $ERRORS{$status} < $ERRORS{'WARNING'}  )) { $status ='WARNING'; }
    if (( $io_wait > $opt_c )and( $ERRORS{$status} < $ERRORS{'CRITICAL'} )) { $status ='CRITICAL'; }
  
    my $r_unit = "B";
    if ( $r_bytes > 1024 * 4 ) { $r_bytes /= 1024; $r_unit="kB"; }
    if ( $r_bytes > 1024 * 4 ) { $r_bytes /= 1024; $r_unit="MB"; }
    if ( $r_bytes > 1024 * 4 ) { $r_bytes /= 1024; $r_unit="GB"; }
    if ( $r_bytes > 1024 * 4 ) { $r_bytes /= 1024; $r_unit="TB"; }

    my $w_unit = "B";
    if ( $w_bytes > 1024 * 4 ) { $w_bytes /= 1024; $w_unit="kB"; }
    if ( $w_bytes > 1024 * 4 ) { $w_bytes /= 1024; $w_unit="MB"; }
    if ( $w_bytes > 1024 * 4 ) { $w_bytes /= 1024; $w_unit="GB"; }
    if ( $w_bytes > 1024 * 4 ) { $w_bytes /= 1024; $w_unit="TB"; }

    $devices{$k}->{return_txt} = sprintf "$k=(%d/%.1f$r_unit/%.1f$w_unit)",$io_wait,$r_bytes,$w_bytes;
  }

  return $status;
}



__END__



