#!/usr/bin/perl
use strict;

# this is the iostat subprocess for mogstored.  see 'mogstored' for copyright & licensing info.
# (C) 2007 Six Apart, Ltd.

my $docroot = $ENV{MOG_DOCROOT};
die "\$ENV{MOG_DOCROOT} not set"                unless $docroot;
die "\$ENV{MOG_DOCROOT} not set to a directory" unless -d $docroot;

# (runs in exec'd child process)
$0 = "mogstored [iostat]";
select((select(STDOUT), $|++)[0]);

my $iostat_pid;
$SIG{TERM} = $SIG{INT} = sub {
    kill 9, $iostat_pid if $iostat_pid;
    exit(0);
};

my $get_iostat_fh = sub {
    while (1) {
        if ($iostat_pid = open (my $fh, "iostat -dx 1 30|")) {
            return $fh;
        }
        # TODO: try and find other paths to iostat
        warn "Failed to open iostat: $!\n"; # this will just go to /dev/null, but will be straceable
        sleep 10;
    }
};

while (1) {
    my $iofh = $get_iostat_fh->();
    my $mog_sysid = mog_sysid_map();  # 5 (mogdevid) -> 2340 (os devid)
    my $dev_sysid = {};  # hashref, populated lazily:  { /dev/sdg => system dev_t }
    my %devt_util;  # dev_t => 52.55
    my $init = 0;
    while (<$iofh>) {
        if (m/^Device:/) {
            %devt_util = ();
            $init = 1;
            next;
        }
        next unless $init;
        if (m/^ (\S+) .*? ([\d.]+) \n/x) {
            my ($devnode, $util) = ("/dev/$1", $2);
            unless (exists $dev_sysid->{$devnode}) {
                $dev_sysid->{$devnode} = (stat($devnode))[6]; # rdev
            }
            my $devt = $dev_sysid->{$devnode};
            $devt_util{$devt} = $util;
            next;
        }
        # blank line is the end.
        if (m!^\s*\n!) {
            $init = 0;
            my $ret = "";
            foreach my $mogdevid (sort { $a <=> $b } keys %$mog_sysid) {
                my $devt = $mog_sysid->{$mogdevid};
                my $ut = defined $devt_util{$devt} ? $devt_util{$devt} : "-";
                $ret .= "$mogdevid\t$ut\n";
            }
            $ret .= ".\n";
            print $ret;
            next;
        }
    }
}

# (runs in iostat child process)
#  returns hashref of { 5 => dev_t device }  # mog_devid -> os_devid
sub mog_sysid_map {
    my $path = $docroot;
    $path =~ s!/$!!;

    # find all devices below us
    my @devnum;  # integer ids
    opendir(my $d, $path) or die "Failed to open docroot: $path: $!";
    @devnum = map { /^dev(\d+)$/ ? $1 : () } readdir($d);

    my $map = {};
    foreach my $mogdevid (@devnum) {
        my ($osdevid) = (stat("$path/dev$mogdevid"))[0];
        $map->{$mogdevid} = $osdevid;
    }

    if (lc($^O) eq 'linux') {
        # name_to_number and number_to_name are the data derived from /proc/partitions
        my %name_to_number; # ( hda1 => 769,  ... )
        my %number_to_name; # ( 769  => hda1, ... )

        if (open my $partitions, '<', '/proc/partitions') {
            <$partitions>; <$partitions>; # First two lines are for humans
            while (my $line = <$partitions>) {
                next unless $line =~ m/^ \s* (\d+) \s+ (\d+) \s+ \d+ \s+ (\S+) \s* $/x;
                my ($major, $minor, $devname) = ($1, $2, $3);
                my $devno = ($major << 8) + $minor;
                $name_to_number{$devname} = $devno;
                $number_to_name{$devno} = $devname;
            }
        } else {
            warn "Unable to open /proc/partitions: $!";
        }

        # Iterate over the hash { 1 => 768 } meaning (mogile device dev1 points to os device 768)
        foreach my $mogdevid (keys %$map) {
            # Look up the original device number
            my $original = $map->{$mogdevid};

            # See if there is a mapping to turn it into a device name (eg. hda1)
            my $devname = $number_to_name{$original} or next;

            # Pull off the new device name with a regex
            if (my ($newname) = $devname =~ m/^([hs]d\w)\d+$/) {
                # Skip if we can't map it back to a device number
                my $newnum = $name_to_number{$newname} or next;
                $map->{$mogdevid} = $newnum;
            }
        }
    }
    return $map;
}

