#!/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;
}
このトピックへフィードバック