#!/usr/bin/perl

use strict;
use warnings FATAL => 'all';

use Text::CSV;
use Data::Dumper;

$| = 1;

my $output = $ARGV[0];
unless (defined($output)) {
    print "usage: $0 output.csv\n";
    exit 1;
}

#my @devs = qw(md10 md11 md12);
my @devs = qw(sda1 sdc1 sdd1);
my ($min, $max) = (2, 14);

my $csv = Text::CSV->new( { binary => 1 });
$csv->eol("\n");
open (my $fh, ">", $output) or die "cannot write to $output: $!";

# read data
my $buf = read_buf("/dev/urandom", 768 * 1024 * 1024);

for (my $i = $min; $i <= $max; ++$i) {
    my ($time, $ops) = chunk_test(\@devs, $i, $buf);
    $csv->print($fh, [ $i, $time, @$ops ]);
}
close($fh) or die "error closing $output: $!";
exit 0;

sub chunk_test {
    my ($devs, $power, $buf) = @_;

    # destroy old array
    rc(qw(mdadm --stop /dev/md3 || true));
    foreach my $dev (@$devs) {
        rc(qw(mdadm --zero-superblock), "/dev/$dev");
    }

    # create new array and trigger a resync
    my $size = 2**$power;
    rc(qw(mdadm -C /dev/md3 -n 3 -l 5 -c), 2**$power, map { "/dev/$_" } @$devs);
    rc(qw(dd if=/dev/zero bs=1c count=1 of=/dev/md3));

    # wait until ready
    rc(qw(sync));
    while (defined(my $sync = check_syncing("md3"))) {
        print "array still syncing: $sync\n";
        sleep 3;
    }
    rc(qw(sync));
    rc(qw(echo 3 > /proc/sys/vm/drop_caches));

    # write and gather data
    my $pre_stats = get_all_stats(@$devs);
    my $time = time;
    write_buf("/dev/md3", $buf);
    rc(qw(sync));
    $time = time - $time;
    my $post_stats = get_all_stats(@$devs);
    my $diff = sub_arrays($post_stats, $pre_stats);
    print_stats($diff);
    return ($time, $diff);
}


sub check_syncing {
    my $dev = shift;
    my $sync = read_line("/sys/block/$dev/md/sync_completed");
    chomp($sync);
    #print "sync: $sync\n";
    return $sync eq "none" ? undef : $sync;
}

sub get_all_stats {
    return sum_arrays( map { get_stats($_) } @_ );
}

sub get_stats {
    my $disk = shift;
    my $statfile = "/proc/diskstats";
    open(my $fh, "<", $statfile) or die "cannot open $statfile: $!";
    while (defined(my $line = <$fh>)) {
        if ($line =~ /.*$disk ([0-9 ]+)/) {
            close($fh) or die "error closing $statfile: $!";
            #print "stats for $disk: $1\n";
            return [ split(/ /, $1) ];
        }
    }
    die "could not find $disk in $statfile";
}

sub print_stats {
    my $row = shift;
    my ($reads, $writes) = ($row->[0], $row->[4]);
    my $perc = $writes ? 100 * $reads / $writes : 0;
    printf("reads: %10d  writes: %10d  %%reads: %4.1f\n",
        $reads, $writes, $perc);
}

sub rc {
    my ($out, $stat) = run(@_);
    print $out if defined($out);
    if ($stat) {
        print "failed, quitting\n";
        exit(1);
    }
    return $out;
}

sub read_buf {
    my ($file, $want) = @_;
    open(my $fh, "<", $file) or die "cannot read $file: $!";
    my $buf;
    print "reading $want bytes from $file...\n";
    my $len = sysread($fh, $buf, $want);
    die "read only $len from $file" if $len != $want;
    close($fh) or die "error closing $file: $!";
    return \$buf;
}


sub read_line {
    my $file = shift;
    open(my $fh, "<", $file) or die "cannot read $file: $!";
    my $line = <$fh>;
    close($fh) or die "error closing $file: $!";
    return $line;
}

sub run {
    print "running: @_\n";
    my $out = `@_ 2>&1`;
    return ($out, $? >> 8);
}

sub sub_arrays {
    my ($a1, $a2) = @_;
    my @out;
    for (my $i = 0; $i < @$a1; ++$i) {
        $out[$i] = $a1->[$i] - $a2->[$i];
    }
    return \@out;
}

sub sum_arrays {
    my $a1 = shift;
    my @out = @$a1;
    while (my $next = shift) {
        for (my $i = 0; $i < @out; ++$i) {
            $out[$i] += $next->[$i];
        }
    }
    return \@out;
}

sub write_buf {
    my ($file, $buf) = @_;
    open(my $fh, ">", $file) or die "cannot write to $file: $!";
    my $want = length($$buf);
    print "writing $want bytes to $file...\n";
    my $len = syswrite($fh, $$buf);
    if (!defined($len) || $len != $want) {
        die "failed to write buffer to $file: $!";
    }
    close($fh) or die "error closing $file: $!";
}
