diskchecker.pl
#!/usr/bin/perl | |
# | |
# Brad's el-ghetto do-our-storage-stacks-lie?-script | |
# | |
sub usage { | |
die <<'END'; | |
Usage: diskchecker.pl -s <server[:port]> verify <file> | |
diskchecker.pl -s <server[:port]> create <file> <size_in_MB> | |
diskchecker.pl -l [port] | |
END | |
} | |
use strict; | |
use IO::Socket::INET; | |
use IO::Handle; | |
use Getopt::Long; | |
use Socket qw(IPPROTO_TCP TCP_NODELAY); | |
my $server; | |
my $listen; | |
usage() unless GetOptions('server=s' => \$server, | |
'listen:5400' => \$listen); | |
usage() unless $server || $listen; | |
usage() if $server && $listen; | |
# LISTEN MODE: | |
listen_mode($listen) if $listen; | |
# CLIENT MODE: | |
my $LEN = 16 * 1024; # 16kB (same as InnoDB page) | |
my $mode = shift; | |
usage() unless $mode =~ /^verify|create$/; | |
my $file = shift or usage(); | |
my $size; | |
if ($mode eq "create") { | |
$size = shift or usage(); | |
} | |
$server .= ":5400" unless $server =~ /:/; | |
my $sock = IO::Socket::INET->new(PeerAddr => $server) | |
or die "Couldn't connect to host:port of '$server'\n"; | |
setsockopt($sock, IPPROTO_TCP, TCP_NODELAY, pack("l", 1)) or die; | |
create() if $mode eq "create"; | |
verify() if $mode eq "verify"; | |
exit 0; | |
sub verify { | |
sendmsg($sock, "read"); | |
my $error_ct = 0; | |
my %error_ct; | |
my $size = -s $file; | |
my $max_pages = int($size / $LEN); | |
my $percent; | |
my $last_dump = 0; | |
my $show_percent = sub { | |
printf " verifying: %.02f%%\n", $percent; | |
}; | |
open (F, $file) or die "Couldn't open file $file for read\n"; | |
while (<$sock>) { | |
chomp; | |
my ($page, $good, $val, $ago) = split(/\t/, $_); | |
$percent = 100 * $page / ($max_pages || 1); | |
my $now = time; | |
if ($last_dump != $now) { | |
$last_dump = $now; | |
$show_percent->(); | |
} | |
next unless $good; | |
my $offset = $page * $LEN; | |
sysseek F, $offset, 0; | |
my $buf; | |
my $rv = sysread(F, $buf, $LEN); | |
my $tobe = sprintf("%08x", $val) x ($LEN / 8); | |
substr($tobe, $LEN-1, 1) = "\n"; | |
unless ($buf eq $tobe) { | |
$error_ct{$ago}++; | |
$error_ct++; | |
print " Error at page $page, $ago seconds before end.\n"; | |
} | |
} | |
$show_percent->(); | |
print "Total errors: $error_ct\n"; | |
if ($error_ct) { | |
print "Histogram of seconds before end:\n"; | |
foreach (sort { $a <=> $b } keys %error_ct) { | |
printf " %4d %4d\n", $_, $error_ct{$_}; | |
} | |
} | |
} | |
sub create { | |
open (F, ">$file") or die "Couldn't open file $file\n"; | |
my $ioh = IO::Handle->new_from_fd(fileno(F), "w") | |
or die; | |
my $pages = int( ($size * 1024 * 1024) / $LEN ); # 50 MiB of 16k pages (3200 pages) | |
my %page_hit; | |
my $pages_hit = 0; | |
my $uniq_pages_hit = 0; | |
my $start = time(); | |
my $last_dump = $start; | |
while (1) { | |
my $rand = int rand 2000000; | |
my $buf = sprintf("%08x", $rand) x ($LEN / 8); | |
substr($buf, $LEN-1, 1) = "\n"; | |
my $pagenum = int rand $pages; | |
my $offset = $pagenum * $LEN; | |
sendmsg($sock, "pre\t$pagenum\t$rand"); | |
# now wait for acknowledgement | |
my $ok = readmsg($sock); | |
die "didn't get 'ok' from server ($pagenum $rand), msg=[$ok] = $!" unless $ok eq "ok"; | |
sysseek F,$offset,0; | |
my $wv = syswrite(F, $buf, $LEN); | |
die "return value wasn't $LEN\n" unless $wv == $LEN; | |
$ioh->sync or die "couldn't do IO::Handle::sync"; # does fsync | |
sendmsg($sock, "post\t$pagenum\t$rand"); | |
$pages_hit++; | |
unless ($page_hit{$pagenum}++) { | |
$uniq_pages_hit++; | |
} | |
my $now = time; | |
if ($now != $last_dump) { | |
$last_dump = $now; | |
my $runtime = $now - $start; | |
printf(" diskchecker: running %d sec, %.02f%% coverage of %d MB (%d writes; %d/s)\n", | |
$runtime, | |
(100 * $uniq_pages_hit / $pages), | |
$size, | |
$pages_hit, | |
$pages_hit / $runtime, | |
); | |
} | |
} | |
} | |
sub readmsg { | |
my $sock = shift; | |
my $len; | |
my $rv = sysread($sock, $len, 1); | |
return undef unless $rv == 1; | |
my $msg; | |
$rv = sysread($sock, $msg, ord($len)); | |
return $msg; | |
} | |
sub sendmsg { | |
my ($sock, $msg) = @_; | |
my $rv = syswrite($sock, chr(length($msg)) . $msg); | |
my $expect = length($msg) + 1; | |
die "sendmsg failed rv=$rv, expect=$expect" unless $rv == $expect; | |
return 1; | |
} | |
sub listen_mode { | |
my $listen_port = shift; | |
my $server = IO::Socket::INET->new(ReuseAddr => 1, | |
Listen => 1, | |
LocalPort => $listen_port) | |
or die "couldn't make server socket\n"; | |
while (1) { | |
print "[server] diskchecker.pl: waiting for connection...\n"; | |
my $sock = $server->accept() | |
or die " die: no connection?"; | |
setsockopt($sock, IPPROTO_TCP, TCP_NODELAY, pack("l", 1)) or die; | |
fork and next; | |
process_incoming_conn($sock, $listen_port); | |
exit 0; | |
} | |
} | |
sub process_incoming_conn { | |
my $sock = shift; | |
my $listen_port = shift; | |
my $peername = getpeername($sock) or | |
die "connection not there?\n"; | |
my ($port, $iaddr) = sockaddr_in($peername); | |
my $ip = inet_ntoa($iaddr); | |
my $file = "/tmp/$ip.diskchecker_$listen_port"; | |
die "[$ip] $file is a symlink" if -l $file; | |
print "[$ip] New connection\n"; | |
my $lines = 0; | |
my %state; | |
my $end; | |
while (1) { | |
if ($lines) { | |
last unless wait_for_readability(fileno($sock), 3); | |
} | |
my $line = readmsg($sock); | |
last unless $line; | |
if ($line eq "read") { | |
print "[$ip] Sending state info from ${ip}'s last create.\n"; | |
open (S, "$file") or die "Couldn't open $file for reading."; | |
while (<S>) { | |
print $sock $_; | |
} | |
close S; | |
print "[$ip] Done.\n"; | |
exit 0; | |
} | |
$lines++; | |
my $now = time; | |
$end = $now; | |
my ($state, $pagenum, $rand) = split(/\t/, $line); | |
if ($state eq "pre") { | |
$state{$pagenum} = [ 0, $rand+0, $now ]; | |
sendmsg($sock, "ok"); | |
} elsif ($state eq "post") { | |
$state{$pagenum} = [ 1, $rand+0, $now ]; | |
} | |
print "[$ip] $lines writes\n" if $lines % 1000 == 0; | |
} | |
print "[$ip] Writing state file...\n"; | |
open (S, ">$file") or die "Couldn't open $file for writing."; | |
foreach (sort { $a <=> $b } keys %state) { | |
my $v = $state{$_}; | |
my $before_end = $end - $v->[2]; | |
print S "$_\t$v->[0]\t$v->[1]\t$before_end\n"; | |
} | |
print "[$ip] Done.\n"; | |
} | |
sub wait_for_readability { | |
my ($fileno, $timeout) = @_; | |
return 0 unless $fileno && $timeout; | |
my $rin; | |
vec($rin, $fileno, 1) = 1; | |
my $nfound = select($rin, undef, undef, $timeout); | |
return 0 unless defined $nfound; | |
return $nfound ? 1 : 0; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment