#!/usr/bin/perl
#
#  $Id: sm_iomon 30138 2014-01-24 13:19:37Z teetov $
# -----------------------------------------------------------------------------
#  SM_IOMON - monitor iostat for spindles
# -----------------------------------------------------------------------------
#  Author: Alex Titov
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
#  ATTENTION! linux specific! expects following output from iostat -tdmx:
#
#Device:  rrqm/s   wrqm/s   r/s   w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await  svctm  %util
#sda      0.01     0.57  0.04  0.17     0.28     1.33    15.70     0.00    4.49   0.62   0.01
#dm-0     0.00     0.00  0.01  0.16     0.03     0.16     2.27     0.00   21.23   0.11   0.00
#dm-1     0.00     0.00  0.00  0.14     0.01     0.58     8.07     0.00    0.86   0.24   0.00
#
# for REQ see: Rally S623:Storage Manager TA1111: IO monitor
#   0. Note: Linux specific. May need be revritten for OSX, Solaris
#   1. Fist cycle from iostat should be skipped
#   2. Collect stat info for each volume in wheels [$APL/var/sm/stat/uuid.stat]
#   3. Collect summary info for wheels group [$APL/var/sm/stat/wheels.stat]
#  
#   TBD: log cleanup in $APL/var/log/sm/stat ! ( or use RRD)
#
use warnings;
use strict;
use List::Util qw(sum);
use POE qw(Wheel::Run Filter::Line);
use Data::Dumper;
use File::Basename qw(dirname);
use lib dirname(__FILE__).'/../lib';              # find  SM::Config here
use SM::Config ':all';
#sleep 200; exit 0;
# CONS ------------------------------------------------------------------------
my $IOMON     =(split(/\//,$0))[-1];	        # the actual name of the prog
my $APL       =$ENV{APL};
my $INTERVAL  ="30";                            # 30 (secounds) is recommended
my $IOSTAT    =(SM_OS eq 'Linux')?"iostat -tdmx $INTERVAL"
               :dirname(__FILE__)."/sm_iostat $INTERVAL"; #simulate iostat
my %TOTAL;
my $SMLOG     ="$APL/var/log/sm/stat";          # location for stat log files
my $ALIASES   ="$APL/var/sm/aliases";           # disks names: disk1 disk2 ..
my $SYSTEM_IO ="$APL/var/log/system_io.log";    # all io log
# VARS ------------------------------------------------------------------------
my $vol;#           #configs of active volumes ex: $vol->{$uuid}->{LIMIT_WRITE}


# SUBS ------------------------------------------------------------------------
sub write_stat {                            # write stat info into file and log
   my  ($id,$stat)=@_;
   SM_LOG->debug("write_stat for $id");
   my  $space=SM_SpaceInfo(SM_MNT."/$id");  # get disk usage
   my  $tm=time;                            # current time
   my  $smstat=SM_STAT;
   #--------------------------------------- stat
   open( STAT,">$smstat/$id.stat") ||  die ("Cannot open $smstat/$id.stat");
   print STAT "TIME=$tm\n";
   print STAT "$_=$stat->{$_}\n"  foreach (keys %$stat);
   print STAT "$_=$space->{$_}\n" foreach (keys %$space);
   close STAT;
   #--------------------------------------- log RRD?
   open( LOG,">>$SMLOG/$id.log") ||  die ("Cannot open $SMLOG/$id.log");    
   print LOG "TIME=$tm ";
   print LOG "$_=$stat->{$_} "  foreach (keys %$stat);
   print LOG "$_=$space->{$_} " foreach (keys %$space);
   print LOG "\n";
   close LOG;
   #--------------------------------------- update TOTAL
   $TOTAL{READ}+= $stat->{READ}  if $stat->{READ}  >0;
   $TOTAL{WRITE}+=$stat->{WRITE} if $stat->{WRITE} >0;
   $TOTAL{SIZE}+= $space->{SIZE} if $space->{SIZE} >0;
   $TOTAL{FREE}+= $space->{FREE} if $space->{FREE} >0;
   $TOTAL{USED}+= $space->{USED} if $space->{FREE} >0;
   $TOTAL{RATE}=int(100*$TOTAL{FREE}/$TOTAL{SIZE}) if $TOTAL{SIZE} >0;
}

sub write_total {                           # write stat info into file and log
   my  $tm=time;                            # current time
   my  $smstat=SM_STAT;
   #--------------------------------------- stat
   open( STAT,">$smstat/wheels.stat") ||  die ("Cannot open $smstat/wheels.stat");
   print STAT "TIME=$tm\n";
   print STAT "$_=$TOTAL{$_}\n"  foreach (keys %TOTAL);
   close STAT;
   #--------------------------------------- log RRD?
   open( LOG,">>$SMLOG/wheels.log") ||  die ("Cannot open $SMLOG/wheels.log");
   print LOG "TIME=$tm ";
   print LOG "$_=$TOTAL{$_} "  foreach (keys %TOTAL);
   print LOG "\n";
   close LOG;
}

# EVENTS-----------------------------------------------------------------------
sub _start {
    my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];

    $heap->{iostat} = POE::Wheel::Run->new
      ( Program =>   $IOSTAT,                        # Program to run.
        StdioFilter  => POE::Filter::Line->new(),    # iostat speaks in lines.
        StderrFilter => POE::Filter::Line->new(),    # iostat speaks in lines.
        StdoutEvent  => "got_iostat_stdout",         # iostat wrote to STDOUT.
        StderrEvent  => "got_iostat_stderr",         # iostat wrote to STDERR.
        CloseEvent   => "got_iostat_close",          # iostat stopped writing.
      );
     $heap->{cycle}=0;
}

my @all;

my ($prev_idle,$prev_total)=(0,0);
sub write_all {
    my $tm=scalar gmtime(time-30);
    my $avg="cpu-load-avg: none";
    if(open(AVG,"/proc/loadavg")) {
      $_=<AVG>;
      chomp;
      close AVG;
      $avg="cpu-load-avg: $_";
    };

    open( ALL,">>$SYSTEM_IO") ||  die ("Cannot open $SYSTEM_IO");
    print ALL "$tm # $avg\n";
    #CPU usage calculation
    if(open(STAT, '/proc/stat')) {
      while (<STAT>) {
        next unless /^cpu\s+[0-9]+/;
        my @cpu = split /\s+/, $_;
        shift @cpu;
        my $idle = $cpu[3];
        my $total = sum(@cpu);
        my $diff_idle = $idle - $prev_idle;
        my $diff_total = $total - $prev_total;
        my $diff_usage = 100 * ($diff_total - $diff_idle) / $diff_total;
        my $time=time;
        printf ALL "$tm | $time # cpu-usage: %0.2f%% \n",$diff_usage;
        ($prev_idle,$prev_total)=($idle,$total);
      }
      close STAT;
    }
    foreach (@all) {
      next if not $_;
      next if /^dm-\d/;
      print ALL "$tm # $_\n";
    }
    close ALL;
    @all=''; # empty
}

# Deal with information the iostat wrote to its STDOUT.

sub got_iostat_stdout {
    my $heap=$_[HEAP];
    $_ = $_[ARG0];
    if (/^(Device|Start):/)  {                     # this is a new cycle
      write_all;
      $heap->{cycle}++;                            # count iostart cycles
      $vol=SM_Wheels();                            # re-read vols every cycle
      my %aliases=map{($vol->{$_}->{minfo_ALIAS}=>1)} keys %$vol; 
      if(open(ALIASES,">$ALIASES")) {
        print ALIASES join(' ',keys %aliases)."\n";
        print ALIASES Dumper $vol;
        close ALIASES;
      }else{
        SM_LOG->Error("Cannot open >$ALIASES");
        unlink "$ALIASES";                         # old definitions may be wrong!
      }
      %TOTAL=(READ=>0,WRITE=>0,SIZE=>0,FREE=>0,USED=>0); #clean on new cycle      
      SM_WritePid($IOMON);
      return;
    }
    return           if $heap->{cycle}<2;          # skip first cycle
    write_total      if /^$/;                      # end of cycle
    return if not /^(\S+)\s+(\S+\s+){4}(\d+\.\d+)\s+(\d+\.\d+)\s+\S+\s+\S+\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)/;
    my %stat=(ALIAS=>$1,READ=>$3,WRITE=>$4,AWAIT=>$5,SVCTM=>$6,UTIL=>$7);
    push @all,$_;
    foreach (keys %$vol) {                         # multiple possible
      if($vol->{$_}->{minfo_ALIAS} eq $stat{ALIAS}) {
        #print "$_ ". join(' ',%stat) ."\n";
        write_stat($_,\%stat);
      }
    }
}

# Deal with information iostat wrote to its STDERR.  These are
# warnings and possibly error messages.

sub got_iostat_stderr {
    my $stderr = $_[ARG0];
    $stderr =~ tr[ -~][]cd;
    print "STDERR: $stderr\n";
}

# The iostat has closed its output filehandles.  It will not be sending
# us any more information, so destroy it.

sub got_iostat_close {
    my $heap = $_[HEAP];
    print "iostat closed.\n";
    delete $heap->{iostat};
}

# A list of functions that will handle the events they're named after.

my @handlers =
  qw( _start got_iostat_stdout got_iostat_stderr got_iostat_close
);

# Start a session where each event is handled by a function with the
# same name.  Run POE's Kernel (and thus all its sessions) until done.
SM_WritePid($IOMON);
POE::Session->create( package_states => [ main => \@handlers ] );
$poe_kernel->run();

