#!/usr/bin/perl

# Watch I/O utilization
#
# Simon Kirby, 2009-05-12
#
# Now with terminal size autodetection! -Simon, 2011-10-20
#
# and fixed VLAN traffic -Simon, 2012-06-21
#
# and fixed handling of NIC counters with delayed updates -Simon, 2012-10-15

use strict;
use POSIX qw[ceil];
sub TIOCGWINSZ () { 0x5413; }
require "sys/ioctl.ph";
use Time::HiRes qw[gettimeofday tv_interval];

my $width = 80;
my $height = 25;
my $swidth;
my $dwidth;
my $block_live;
my $block_dead;
sub sig_winch {
	my $winsize;
        -t STDIN or return;
	ioctl(STDIN, TIOCGWINSZ, $winsize) or return;
	($height,$width) = unpack('S2', $winsize);
	$width = 96 if ($width > 96);
	$swidth = ($width >> 1) - 17;
	$dwidth = ($width & ~1) - 21;
	$block_live = chr(219) x $dwidth;
	$block_dead = chr(176) x $dwidth;
}
$SIG{'WINCH'} = \&sig_winch;
sig_winch();

my $interval = 0.04;
my $stat_rate = 0.2;
my $typical_max_io_rate = 768;
my $typical_max_tx_rate = 1000;
my $iface_traffic_divisor = 1_000_000 / 8;
my $min_device_requests = 512;
my %stat = ();
my %vmstat = ();
my %last_vmstat = ();
my %stats = ();
my %last_stats = ();
my %iface = ();
my %last_iface = ();
my %iface_time = ();
my %iface_rate = ();
my %ravg = ();
my %max = ();
my %realname = ();
my $exp = 1. / (exp($interval / $stat_rate));
my $r_exp = 1. - $exp;

my @dms = ();
if (opendir(DIR,"/dev/mapper")) {
	chdir('/dev/mapper');
	my @dms = readdir(DIR);
	closedir(DIR);
	foreach my $dm (@dms) {
		my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size) = lstat($dm) or next;
		next unless ($rdev > 0);
		my $maj = $rdev >> 8;
		next unless ($maj == 252);
		my $min = $rdev & 0xff;

		# Collapse the long LVS names
		# vg005-vg006_web02_backup -> v5v6w2b
		# vg003-web00 -> vg3web0
		$dm =~ s/vg/v/g;
		$dm =~ s/web/w/g;
		$dm =~ s/backup//g;
		$dm =~ s/shared/s/g;
		$dm =~ s/varlibnfs/nfs/g;
		$dm =~ s/([a-z])0+(\d+)/$1$2/g;
		$dm =~ s/[\-\_]+//g;
		$realname{$maj}{$min} = $dm;
	}
}
chdir('/');

print "\033[2J\033(U";

for (;;select '','','',$interval) {
	print "\033[1;1H";

	open(IN,"< /proc/loadavg") or die;
	chomp(my $x = <IN>);
	print "Load: $x\033[K\n";
	close(IN);
	
	%vmstat = ();
	open(IN,"< /proc/stat") or die;
	while (<IN>) {
		chomp;
		my @kv = split(' ',$_,2);
		$stat{$kv[0]} = $kv[1];
	}

	close(IN);
	unless (scalar %last_vmstat) {
		%last_vmstat = %vmstat;
	}
	 
	%vmstat = ();
	open(IN,"< /proc/vmstat") or die;
	while (<IN>) {
		chomp;
		my @kv = split(' ',$_,2);
		$vmstat{$kv[0]} = $kv[1];
	}
	close(IN);

	if (0) {
	my %vmstat_delta = ();
	if (scalar %last_vmstat) {
		foreach my $k (keys %vmstat) {
			my $d = $vmstat{$k} - $last_vmstat{$k};
			$vmstat_delta{$k} = $d;
		}
	}
	}

	foreach my $k (qw[dirty writeback]) {
		my $nr_k = "nr_$k";
		my $v = $vmstat{$nr_k};
		my $max = $v;

		$max = $dwidth if ($max < $dwidth);
		$max = $v if ($v > $max);
		$max{$nr_k} = $max if (!defined($max{$nr_k}) or $max > $max{$nr_k});
		$max = $max{$nr_k} if ($max < $max{$nr_k});
		my $w_nr_k = ceil($v * $dwidth / $max);
		my $x = $k;
		$x =~ s/writeback/wrback/;
		printf("%6s #\033[1;32m%.*s\033[0m%.*s %u\033[K\n",
			$x,$w_nr_k,$block_live,$dwidth - $w_nr_k,$block_dead,$v);
	}

	my $sweeptime = [gettimeofday];
	open(IN,"/proc/net/dev") or open(IN, "/proc/partitions") or die;
	my @devs = ();
	while (<IN>) {
		#  eth1:4117866583283 1390525869    0  202    0     0          0         0 3177891644918 1390452675    0    0    0     0       0          0
		/^\s*(\S+):\s*(\d+)\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)\s+(\d+)/ or next;
		my $dev = $1;
		next if ($dev eq 'lo');
#		next unless ($dev eq 'eth1');

		$iface{$dev}{'rx_bytes'} = $2;
		$iface{$dev}{'rx_packets'} = $3;
		$iface{$dev}{'tx_bytes'} = $4;
		$iface{$dev}{'tx_packets'} = $5;
		my $tdiff;
		foreach my $item (keys %{$iface{$dev}}) {
			if (!defined($last_iface{$dev}{$item})) {
				$last_iface{$dev}{$item} = $iface{$dev}{$item};
				$iface_time{$dev} = $sweeptime;
				$iface_rate{$dev}{$item} = 0;
				next;
			}
			my $diff = $iface{$dev}{$item} - $last_iface{$dev}{$item};
			if ($diff) {
				if ($iface_time{$dev} != $sweeptime) {
					$tdiff = tv_interval($iface_time{$dev}, $sweeptime);
					$iface_time{$dev} = $sweeptime;
					$tdiff = 1 if (!$tdiff);
				}
				$diff+= (1<<32) if ($diff < 0);	# 32-bit wrap correction
				$iface_rate{$dev}{$item} = $diff / $tdiff;
			}
			$last_iface{$dev}{$item} = $iface{$dev}{$item};
		}

		my $rx = $iface_rate{$dev}{'rx_bytes'} / $iface_traffic_divisor;
		my $tx = $iface_rate{$dev}{'tx_bytes'} / $iface_traffic_divisor;
		my $max = ceil($rx + $tx);
		$max = $typical_max_tx_rate if ($max < $typical_max_tx_rate);
		$max = 1 if ($max < 1);
		my $w_rx = int($rx * $dwidth / $max);
		my $w_tx = int($tx * $dwidth / $max);
		$dev =~ s/^vlan/v/;
		printf("%6s T\033[1;31m%.*s\033[0m%.*s\033[1;33m%.*s\033[0mR %4u:%-4u \033[K\n",
			$dev,
			$w_tx,$block_live,
			$dwidth - $w_rx - $w_tx,$block_dead,
			$w_rx,$block_live,
			$tx,$rx);
	}

	open(IN,"/proc/diskstats") or open(IN, "/proc/partitions") or die;
	my @devs = ();
	while (<IN>) {
		# 152      48 etherd/e10.0 24410779 4139165 446213121 450821528 7960721 81162038 715309760 2166892060 33 75017336 
		/^\s*(\d+)\s+(\d+)\s+([[:alpha:][:digit:]\/\.\-]+)\s+(.*)/ or next;

		my $maj = $1;
		my $min = $2;
		my $dev = $3;
		my @fields = split(/\s+/, $4);

		next if ($fields[9] < $min_device_requests);

		if (defined($realname{$maj}{$min})) {
			$dev = $realname{$maj}{$min};
		} else {
			$dev =~ s/\/disc$//;
			$dev =~ s/etherd\///;
		}

		push(@devs,$dev);
		%{$stats{$dev}} = (
			'rio' => $fields[0],
			'rmerge' => $fields[1],
			'rsect' => $fields[2],
			'ruse' => $fields[3],
			'wio' => $fields[4],
			'wmerge' => $fields[5],
			'wsect' => $fields[6],
			'wuse' => $fields[7],
			'running' => $fields[8],
			'use' => $fields[9],
			'aveq' => $fields[10],
			'max_running' => (defined($stats{$dev}{'max_running'}) ? $stats{$dev}{'max_running'} : $swidth),
		);
	}
	close(IN);

#	use Data::Dumper;
#	print Dumper(\%stats);

	if (!scalar %last_stats) {
		foreach my $dev (@devs) {
			foreach my $item (keys %{$stats{$dev}}) {
				$last_stats{$dev}{$item} = $stats{$dev}{$item};
			}
		}
	}

	foreach my $dev (sort @devs) {
		my $running = $stats{$dev}{'running'};
# This would be the same except that our sleep skews.
#                $running = ceil(($stats{$dev}{'aveq'} - $last_stats{$dev}{'aveq'}) / ($interval * 1000));
		if ($running > $stats{$dev}{'max_running'}) {
			$stats{$dev}{'max_running'} = $running;
		}
		my $w_running = ceil($running * $swidth / $stats{$dev}{'max_running'});
		my $rio = ($stats{$dev}{'rio'} - $last_stats{$dev}{'rio'}) / $interval;
		$rio = $ravg{$dev}{'rio'} = $ravg{$dev}{'rio'} * $exp + $rio * $r_exp;
		my $wio = ($stats{$dev}{'wio'} - $last_stats{$dev}{'wio'}) / $interval;
		$wio = $ravg{$dev}{'wio'} = $ravg{$dev}{'wio'} * $exp + $wio * $r_exp;
		my $max = ceil($rio + $wio);
		$max = $typical_max_io_rate if ($max < $typical_max_io_rate);
		$max = 1 if ($max < 1);
		my $w_rio = int($rio * $swidth / $max);
		my $w_wio = int($wio * $swidth / $max);
		printf("%6s W\033[1;31m%.*s\033[0m%.*s\033[1;33m%.*s\033[0mR %4u:%-4u Q\033[1m%.*s\033[0m%.*s %-6s \033[K\n",
			$dev,
			$w_wio,$block_live,
			$swidth - $w_rio - $w_wio,$block_dead,
			$w_rio,$block_live,
			$wio,$rio,
			$w_running,$block_live,$swidth - $w_running,$block_dead,$running);
		foreach my $item (keys %{$stats{$dev}}) {
			$last_stats{$dev}{$item} = $stats{$dev}{$item};
		}
	}

	foreach my $k (qw[blocked]) {
		my $nr_k = "procs_$k";
		my $v = $stat{$nr_k};
		my $max = $v;

		$max = $dwidth if ($max < $dwidth);
		$max = $v if ($v > $max);
		$max{$nr_k} = $max if (!defined($max{$nr_k}) or $max > $max{$nr_k});
		$max = $max{$nr_k} if ($max < $max{$nr_k});
		my $w_nr_k = ceil($v * $dwidth / $max);
		my $x = $k;
		$x =~ s/blocked/blockd/;
		printf("%6s #\033[1;32m%.*s\033[0m%.*s %u\033[K\n",
			$x,$w_nr_k,$block_live,$dwidth - $w_nr_k,$block_dead,$v);
	}
                                                                                                                                                                                                                        
	print "\033[0J";
	$| = 1;
	$| = 0;
}
