#!/usr/bin/perl

use strict;
use warnings;

use Time::HiRes qw( gettimeofday tv_interval );

# 0. Initialize
our $start_time = [gettimeofday()];
my $data = {};
my @accepted_commands =
( 'import.pl',
  'HandBrakeCLI',
  'hive2_ffmpegsvn',
  'mediainfo',
  'cp'
);
my $accepted_commands_pattern = '(' . join('|', @accepted_commands) . ')';

print '='x80 . "\n";
print '='x30 . '    IOTop Report    ' . '='x30 . "\n";
print '='x80 . "\n\n";

# 1. Read in arguments
print 'Arguments:' . "\n";
if (!defined $ARGV[0] || !-f $ARGV[0])
{
  &printError('Missing iotop log file or not a file.');
  &printUsage();
  &exitProgram();
}
my $iotop_file = $ARGV[0];
print "\t" . 'IOTop file: ' . $iotop_file . "\n";
print "\n";

# 2. Loop through iolog file
print 'Process:' . "\n";
print "\t" . ' * Reading IOTop log file... ';
my $iolog_fh;
open($iolog_fh, '<:utf8', $iotop_file);
if ($iolog_fh)
{
  my $line = '';
  while ($line = <$iolog_fh>)
  {
    # Look for lines describing process IO activity
    if ($line =~ /(\d\d:\d\d:\d\d)\s+(\d+)\s+([^\s]+)\s+([^\s]+)\s+(\d+\.\d+\s[BKMG](?:\/s)?)\s+(\d+\.\d+\s[BKMG](?:\/s)?)\s+(\d+\.\d+)\s\%\s+(\d+\.\d+)\s\%\s+(.*)/)
    {
      my $timestamp  = $1;
      my $pid        = $2;
      my $priority   = $3;
      my $user       = $4;
      my $disk_read  = $5;
      my $disk_write = $6;
      my $swap_in    = $7;
      my $io_percent = $8;
      my $command    = $9;
      # Shorten command so it is more ueful
      $command = &parseCommand($command);
      # If we are not ignoring these commands...
      if ($command =~ /$accepted_commands_pattern/)
      {
        # Store this information for a new record
        if (!defined $data->{$pid})
        {
          $data->{$pid} = {'command'    => $command,
                           'start_time' => $timestamp,
                           'end_time'   => $timestamp,
                           'occurances' => 1,
                           'iopercents' => $io_percent
                          };
        }
        # or update an existing record
        else
        {
          $data->{$pid}->{'end_time'} = $timestamp;
          $data->{$pid}->{'occurances'}++;
          $data->{$pid}->{'iopercents'} .= ':' . $io_percent
        }
      }
    }
  }
  close($iolog_fh);
}
else
{
  &printError('Failed to open file for reading: ' . $iotop_file);
  &exitProgram();
}
print 'Done!' . "\n";

# 3. Print report to CSV
print "\t" . ' * Writing report... ' . "\n";;
my $ioreport_fh;
open($ioreport_fh, '>:utf8', 'ioreport.csv');
my $temp_sum = 0;
if ($ioreport_fh)
{
  print 'PID,    DUR, TIME, IOPCT, COMMAND' . "\n";
  foreach my $pid (sort keys %{$data})
  {
    my $start_seconds = &parseEpoc($data->{$pid}->{'start_time'});
    my $end_seconds = &parseEpoc($data->{$pid}->{'end_time'});
    my $duration = $end_seconds - $start_seconds;

    my @io_percents = split(/:/, $data->{$pid}->{'iopercents'});
    my $io_sum = 0.00;
    foreach my $io_percent (@io_percents)
    {
      if ($io_percent > 0)
      {
        $temp_sum += $io_percent;
      }
      $io_sum += $io_percent;
    }
    my $io_avg = 0.00;
    if ($io_sum > 0)
    {
      $io_avg = $io_sum / $data->{$pid}->{'occurances'};
    }
    print sprintf('%5d, %4d, %4d, %5.2f, "%s"', $pid, $duration, $data->{$pid}->{'occurances'}, $io_avg, $data->{$pid}->{'command'}) . " [" . $data->{$pid}->{'iopercents'} . "]\n";
  }
  close($ioreport_fh);
}
else
{
  &printError('Failed to open file for writing: ./ioreport.csv');
}
print 'Done!' . "\n\n";

print "Sum: " . $temp_sum . "\n";

&exitProgram();

# Should never get here
exit;

###### FUNCTIONS ######

## @function
#
sub exitProgram
{
  my $end_time = [gettimeofday()];
  my $elapsed = tv_interval($start_time, $end_time);
  print sprintf('%s  Complete in %7.3f seconds!  %1$s', '='x24, $elapsed) . "\n\n";
  exit;
}
## exitProgram() ##


## @function
#
sub parseCommand
{
  my ($full_command) = @_;
  my $command;
  # Anything that starts with perl, we extract just the script name
  if ($full_command =~ /perl.*?([a-z0-9]+\.pl)/)
  {
    $command = $1;
  }
  elsif ($full_command =~ /^([^\s]+)/)
  {
    $command = $1;
  }
  # Everything else we shorten to just 12 characters
  else
  {
    $command = substr($full_command, 0, 12);
  }
  return $command;
}
## parseCommand() ##


## @function
#
sub parseEpoc
{
  my ($timestamp) = @_;
  my @parts = split(/:/, $timestamp);
  my $seconds = $parts[2];         # Seconds
  $seconds += $parts[1] * 60;      # Minutes
  $seconds += $parts[0] * 60 * 60; # Hours
  return $seconds;
}
## parseEpoc() ##


## @function
#
sub printError
{
  my ($msg) = @_;
  print 'Error! ' . $msg . "\n";
}
## printError() ##


## @function
#
sub printUsage
{
  print 'Usage: iotop_report.pl <iotop log file>' . "\n\n";
}
## printUsage() ##
