#!/usr/bin/perl
#########################################################
#  multitest, by Marcus Sorensen, BetterServers Inc     #
#  Licensed under the Open Software License version 3.0 #
#  http://opensource.org/licenses/OSL-3.0               #
#########################################################
use strict;

$| = 1;
my $colors = { red => "\e[1;31m", def => "\e[0m", green => "\e[1;32m", cyan => "\e[1;36m" };
my $restbetweentests = 15;

unless ( `which fio 2>/dev/null`) {
  print "No executable 'fio' found in path, exiting\n";
  exit;
}

print <<EOF;
$colors->{red}
Multiple IO Tester$colors->{def}

  This application emulates a busy server in several states by launching multiple
threads that do various types of IO. This allows us to see what the consequences
are of running in a multitasking environment. This test uses direct IO and 
invalidates caches between tests, testing the disk, not the memory.

$colors->{red}NOTE:$colors->{def} You need at least 3GB of free space in your current working directory.

The following tests currently consist of:

  8 sequential readers
  8 sequential writers
  8 mixed seqential readers/writers (random choice per IO)
  8 random readers
  8 random writers
  8 mixed random readers/writers (random choice per IO)

Feel free to modify the script to meet your needs. Enjoy!

The test should take less than 2 minutes. Press <ENTER> to begin...
EOF
<STDIN>;

my $tests = { 'read'      => { 'order' => 1, 
			       'block' => '1024k', 
                               'output' => { 'multiiotester'=>'4', '2'=>'5', '3'=>'6' },
                               'name' => 'sequential read' }, 
              'write'     => { 'order' => 2, 
                               'block' => '1024k', 
                               'output' => { 'multiiotester'=>'20', '2'=>'25', '3'=>'47' },
                               'name' => 'sequential write' }, 
              'rw'        => { 'order' => 3, 
                               'block' => '1024k', 
                               'output' => { 'multiiotester'=>'4,20', '2'=>'5,25', '3'=>'6,47' },
                               'name' => 'seq read/seq write' }, 
              'randread'  => { 'order' => 4, 
                               'block' => '4k', 
                               'output' => { 'multiiotester'=>'4', '2'=>'5', '3'=>'6' },
                               'name' => 'random read' }, 
              'randwrite' => { 'order' => 5, 
                               'block' => '4k', 
                               'output' => { 'multiiotester'=>'20', '2'=>'25', '3'=>'47' },
                               'name' => 'random write' } , 
              'randrw'    => { 'order' => 6, 
                               'block' => '4k', 
                               'output' => { 'multiiotester'=>'4,20', '2'=>'5,25', '3'=>'6,47' },
                               'name' => 'rand read/rand write' } 
            };

#mkdir('./multiiotester') or die "unable to create test directory: $^E";
mkdir('./multiiotester') if ! -d './multiiotester';
chdir('./multiiotester') or die "unable to chdir to test directory: $^E";


foreach my $t ( sort{$tests->{$a}->{order} cmp $tests->{$b}->{order}} keys %{$tests} ) {
  print "$colors->{cyan} running IO \"$tests->{$t}->{name}\" test... $colors->{def}\n";

  my $cmd = "fio --direct=1 --invalidate=1 --ioengine=libaio --iodepth=8 --thread --time_based --runtime=10 --rw=$t --bs=$tests->{$t}->{block} --size=300M --numjobs=8 --name=multiiotester --minimal | grep ';'";

  my @output = `$cmd`;
  $output[0] =~ /^(.*?);/;
  my $version = $1;
  my $data;

  foreach my $d (@output){
    next unless $d =~ /;/;
    my $field = $tests->{$t}->{output}->{$version};
    my @items = split(";",$d);
    if ($field =~ /(\d+),(\d+)/) {
      $data .= "$items[$1];$items[$2]\n";
    } else {
      $data .= "$items[$field]\n";
    }
  }

  my @results = split(/;/,combinejobs($data));
  my @iops = map { toiops($_,$tests->{$t}->{block}) } @results;
  @results = map { convert($_) } @results;

  if( $t =~ /^rand/ ) {
    print "\tresult is $colors->{green}" . join("/", @results) . "$colors->{def} per second\n";
    print "\tequals $colors->{green}" . join("$colors->{def}/$colors->{green}", @iops) . "$colors->{def} IOs per second\n\n";
  }
  else {
    print "\tresult is $colors->{green}" . join("$colors->{def}/$colors->{green}", @results) . "$colors->{def} per second\n\n";
  } 
  
  sleep $restbetweentests;
}

#print "cleaning up files..\n";

#unlink glob "multiiotester*";
#chdir("..");
#rmdir("multiiotester") or print "unable to delete directory 'multiiotester'\n";

###########################
####### subroutines #######
###########################

sub convert {
  my $val = shift;
  my @units = ('KB','MB','GB');
  my $i = 0;

  $val =~ /^\d+/;
  while (length($&) > 3 ) {
    $val = sprintf("%.2f",$val / 1024);
    $i++;
    $val =~ /^\d+/;
  }
  return $val . $units[$i];
}

sub toiops {
  my $val = shift;
  my $blocksize = shift;

  $blocksize =~ s/k//;
  my $io = sprintf("%.1f",$val/$blocksize);
 
  return $io;
}

sub combinejobs {
  my $input = shift;
  
  my @lines = split(/\n/,$input);
  my @output = ();

  foreach my $l (0..$#lines) {
    my @temp = split(/;/,$lines[$l]);
    foreach my $t (0..$#temp){
      $output[$t] += $temp[$t];
    }
  }

  return join(";",@output);
}

