#!/usr/bin/perl
# Copyright (c) 2011, SIOS Technology, Corp.
# Author: Paul Clements
use strict;
sub msg {
	printf STDERR _;
}
sub dbg {
	return if (! $ENV{'ROC_DEBUG'});
	msg _;
}
$0 =~ s@^.*/@@; # basename
sub usage {
	msg "Usage: $0 <interval> <start-time> <iostat-data-file> [dev-list]\n";
	msg "\n";
	msg "This utility takes a /proc/diskstats output file that contains\n";
	msg "output, logged over time, and calculates the rate of change of\n";
	msg "the disks in the dataset\n";
	msg "OUTPUT_CSV=1 set in env. dumps the full stats to a CSV file on STDERR\n";
	msg "\n";
	msg "Example: $0 1hour \"jun 23 12pm\" steeleye-iostat.txt sdg,sdh\n";
	msg "\n";
	msg "interval - interval between samples\n";
	msg "start time - the time when the sampling starts\n";
	msg "iostat-data-file - collect this with a cron job like:\n";
	msg "\t0 * * * * (date ; cat /proc/diskstats) >> /root/diskstats.txt\n";
	msg "dev-list - list of disks you want ROC for (leave blank for all)\n";
	exit 1;
}
usage if (ARGV < 3);
my $interval = TimeHuman($ARGV[0]);
my $starttime = epoch($ARGV[1]);
my $file = $ARGV[2];
my $blksize = 512; # /proc/diskstats is in sectors
my %devs = map { $_ => 1 } split /,/, $ARGV[3];
my %stat;
my $firsttime;
my $lasttime;
# datestamp divides output
my %days = ( 'Sun' => 1, 'Mon' => 1, 'Tue' => 1, 'Wed' => 1,
		'Thu' => 1, 'Fri' => 1, 'Sat' => 1);
my %fields = ( 'major'  => 0,
		'minor' => 1,
		'dev' => 2,
		'reads' => 3,
		'reads_merged' => 4,
		'sectors_read' => 5,
		'ms_time_reading' => 6,
		'writes' => 7,
		'writes_merged' => 8,
		'sectors_written' => 9,
		'ms_time_writing' => 10,
		'ios_pending' => 11,
		'ms_time_total' => 12,
		'weighted_ms_time_total' => 13 );
my $devfield = $fields{'dev'};
my $calcfield = $ENV{'ROC_CALC_FIELD'} || $fields{'sectors_written'};
dbg "using field $calcfield\n";
open(FD, "$file") or die "Cannot open $file: $!\n";
foreach (<FD>) {
	chomp;
	_ = split;
	if (exists($days{$_[0]})) { # skip datestamp divider
		if ($firsttime eq '') {
			$firsttime = join ' ', _[0..5];
		}
		$lasttime = join ' ', _[0..5];
		next;
	}
	next if ($_[0] !~ /[0-9]/); # ignore
	if (!%devs || exists $devs{$_[$devfield]}) {
		push {$stat{$_[$devfield]}}, $_[$calcfield];
	}
}
{$stat{'total'}} = totals(\%stat);
printf "Sample start time: %s\n", scalar(localtime($starttime));
printf "Sample end time: %s\n", scalar(localtime($starttime + (({$stat{'total'}} - 1) * $interval)));
printf "Sample interval: %ss #Samples: %s Sample length: %ss\n", $interval, ({$stat{'total'}} - 1), ({$stat{'total'}} - 1) * $interval;
print "(Raw times from file: $firsttime, $lasttime)\n";
print "Rate of change for devices " . (join ', ', sort keys %stat) . "\n";
foreach (sort keys %stat) {
	my vals = {$stat{$_}};
	my ($max, $maxindex, $roc) = roc($_, $blksize, $interval, vals);
	printf "$_ peak:%sB/s (%sb/s) ( %s) average:%sB/s (%sb/s)\n", HumanSize($max), HumanSize($max * 8), scalar localtime($starttime + ($maxindex * $interval)), HumanSize($roc), HumanSize($roc * 8);
}
# functions
sub roc {
	my $dev = shift;
	my $blksize = shift;
	my $interval = shift;
	my ($max, $maxindex, $i, $first, $last, $total);
	my $prev = -1;
	my $first = $_[0];
	if ($ENV{'OUTPUT_CSV'}) { print STDERR "$dev," }
	foreach (_) {
		if ($prev != -1) {
			if ($_ < $prev) {
				dbg "wrap detected at $i ($_ < $prev)\n";
				$prev = 0;
			}
			my $this = ($_ - $prev) * $blksize / $interval;
			if ($this > $max) {
				$max = $this;
				$maxindex = $i;
			}
			if ($ENV{'OUTPUT_CSV'}) { print STDERR "$this," }
		}
		$prev = $_; # store current val for next time around
		$last = $_;
		$i++;
	}
	if ($ENV{'OUTPUT_CSV'}) { print STDERR "\n" }
	return ($max, $maxindex, ($last - $first) * $blksize / ($interval * ($i - 1)));
}
sub totals { # params: stat_hash
	my $stat = shift;
	my totalvals;
	foreach (keys %$stat) {
		next if (!defined($stat{$_}));
		my vals = {$stat{$_}};
		my $i;
		foreach (vals) {
			$totalvals[$i++] += $_;
		}
	}
	return totalvals;
}
# converts to KB, MB, etc. and outputs size in readable form
sub HumanSize { # params: bytes/bits
	my $bytes = shift;
	my suffixes = ( '', 'K', 'M', 'G', 'T', 'P' );
	my $i = 0;
        while ($bytes / 1024.0 >= 1) {
		$bytes /= 1024.0;
		$i++;
	}
	return sprintf("%.1f %s", $bytes, $suffixes[$i]);
}
# convert human-readable time interval to number of seconds
sub TimeHuman { # params: human_time
	my $time = shift;
	my %suffixes = ('s' => 1, 'm' => 60, 'h' => 60 * 60, 'd' => 60 * 60 * 24);
	$time =~ /^([0-9]*)(.*?)$/;
	$time = $1;
	my $suffix = (split //, $2)[0]; # first letter from suffix
	if (exists $suffixes{$suffix}) {
		$time *= $suffixes{$suffix};
	}
	return $time;
}
sub epoch { # params: date
	my $date = shift;
	my $seconds = `date +'%s' --date "$date" 2>&1`;
	if ($? != 0) {
		die "Failed to recognize time stamp: $date\n";
	}
	return $seconds;
}

フィードバック

フィードバックありがとうございました

このトピックへフィードバック

送信