+#!/usr/bin/perl
+# This script is to be run on a client machine and will test all the
+# OSTs to determine which is the fastest and slowest
+# The current test method is as follows:
+# -Create a directory for each OST
+# -Use 'lfs setstripe' to set the Lustre striping such that IO goes to
+# only one OST
+# -Use 'dd' to write and read a file of a specified size
+# -Compute the average, and Standard deviation
+# -Find the slowest OST for read and write
+# -Find the Fastest OST for read and write
+
+# GLOBALS
+$pname = $0; # to hold program name
+$OSTS = 0; # Number of OSTS we will loop over
+$BSIZE = 1024 * 1024; # Size of i/o block
+$MNT = "/mnt/lustre"; # Location of Lustre file system
+$CNT = 0;
+$FSIZE = 30; # Number of i/o blocks
+
+# Usage
+sub usage () {
+ print "Usage: $pname [-s <size>] [-h] <Lustre_Path>\n";
+ print "[OPTIONS]\n";
+ print " -s: size of test file in MB (default $FSIZE MB)\n";
+ print " -h: To display this help\n";
+ print "example : $pname /mnt/lustre\n";
+ exit 1;
+}
+
+# ost_count subroutine ets globle variable $OST with Number of OST's
+# Also fills 1 for active OST indexes in ACTIVEOST_INX array.
+sub ost_count () {
+ # numobd gives number of ost's and activeobd gives number of active ost's
+ my $tempfile = glob ("/proc/fs/lustre/lov/lustre-clilov-*/activeobd");
+ open(PTR, $tempfile) || die "Cannot open $tempfile: $!\n";
+ $OSTS = <PTR>;
+ close PTR;
+ print "Number of Active OST devices : $OSTS";
+ my $tempfile = glob ("/proc/fs/lustre/lov/lustre-clilov-*/numobd");
+ open(PTR, $tempfile) || die "Cannot open $tempfile: $!\n";
+ $numost = <PTR>;
+ close PTR;
+ if ( $numost != $OSTS ) {
+ printf "Number of non active ots(s): %d\n", ( $numost - $OSTS );
+ $OSTS = $numost;
+ }
+ my $tempfile = glob ("/proc/fs/lustre/lov/lustre-clilov-*/target_obd");
+ open(PTR, $tempfile) || die "Cannot open $tempfile: $!\n";
+ my $count = 0;
+ my $temp;
+ while (<PTR>) {
+ chop;
+ my ($ost_num, $ost_name, $ost_status) = split(/\s+/, $_);
+ if ( $ost_status eq "ACTIVE" ) {
+ $ACTIVEOST_INX[$count] = 1;
+ }
+ $count++;
+ }
+}
+
+# make_dummy subroutine creates a dummy file that will be used for read operation.
+sub make_dummy () {
+ my $SIZE = $_[0];
+ my $tempfile = $_[1];
+ system ("dd of=$tempfile if=/dev/zero count=$SIZE bs=$BSIZE 2> /dev/null");
+}
+
+# run_test subroutine actually writes and reads data to/from dummy file
+# and compute corresponding time taken for read and write operation and
+# byte transfer for the both operations.
+# This subroutine also fill corresponding globle arrays with above information.
+sub run_test () {
+ my $SIZE = $_[0];
+ my $INX=$_[1];
+ my $ACTION=$_[2];
+ my $tempfile = $_[3];
+
+ if ( !(-f $tempfile) && $ACTION eq "read" ) {
+ &make_dummy($SIZE, $tempfile);
+ }
+ my $LoadTimeHiRes = "use Time::HiRes qw(gettimeofday)";
+ eval ($LoadTimeHiRes);
+ system("sync");
+ my ($ts0, $tu0) = gettimeofday();
+ $tu0 = $ts0 + ($tu0 / 1000000);
+ if ( $ACTION eq "write" ) {
+ system("dd of=$tempfile if=/dev/zero count=$SIZE bs=$BSIZE 2> /dev/null");
+ } elsif ( $ACTION eq "read" ) {
+ system("dd if=$tempfile of=/dev/null count=$SIZE bs=$BSIZE 2> /dev/null");
+ } else {
+ print "Action is neither read nor write\n";
+ exit 1;
+ }
+ system("sync");
+ my ($ts1, $tu1) = gettimeofday();
+ $tu1 = $ts1 + ($tu1/1000000);
+ my $tdelta = $tu1 - $tu0;
+ my $delta = ($SIZE * $BSIZE / ( $tu1 - $tu0 )) / (1024 * 1024);
+ if ( $ACTION eq "write" ) {
+ $wTime[$INX] = $tdelta;
+ $wMBs[$INX] = $delta;
+ } else {
+ $rTime[$INX] = $tdelta;
+ $rMBs[$INX] = $delta;
+ }
+}
+
+# calculate subroutine compute following things and displays them.
+# - Finds worst and best OST for both read and write operations.
+# - Compute average of read and write rate from all OSTS
+# - Compute Standard deviation for read and write form all OST's
+sub calculate () {
+ my ($op, $MBs);
+ $op = $_[0];
+ @MBs = @_[1..$#_];
+ my $count = 0;
+ my $total = 0;
+ my $avg = 0;
+ my $sd = 0;
+ my $best_OST = 0;
+ my $worst_OST = 0;
+ my $max_mb = 0;
+ my $min_mb = 999999999;
+ while ($count < $OSTS ) {
+ if ( $ACTIVEOST_INX[$count] ) {
+ $total = $total + $MBs[$count];
+ if ($max_mb < $MBs[$count] ) {
+ $max_mb = $MBs[$count];
+ $best_OST = $count;
+ }
+ if ($min_mb > $MBs[$count] ) {
+ $min_mb = $MBs[$count];
+ $worst_OST = $count;
+ }
+ }
+ $count++;
+ }
+ $avg = $total/$OSTS;
+ $total = 0;
+ $count = 0;
+ while ($count < $OSTS ) {
+ if ( $ACTIVEOST_INX[$count] ) {
+ $total = $total + ($MBs[$count] - $avg) * ($MBs[$count] - $avg);
+ }
+ $count++;
+ }
+ $sd = sqrt($total/$OSTS);
+ printf "Worst %s OST indx: %d speed: %f\n", $op, $worst_OST, $min_mb;
+ printf "Best %s OST indx: %d speed: %f\n", $op, $best_OST, $max_mb;
+ printf "%s Avrage: %f +/- %f MB/s\n", $op, $avg, $sd;
+}
+
+# output_all_data subroutine displays speed and time information
+# for all OST's for both read and write operations.
+sub output_all_data () {
+ my $count = 0;
+ print "Ost# Read(MB/s) Write(MB/s) Read-time Write-time\n";
+ print "----------------------------------------------------\n";
+ while ( $count < $OSTS ) {
+ if ( $ACTIVEOST_INX[$count] ) {
+ printf "%d %.3f %.3f %.3f %.3f\n",$count,
+ $rMBs[$count], $wMBs[$count], $rTime[$count], $wTime[$count];
+ } else {
+ printf "%d Inactive ost\n",$count;
+ }
+ $count = $count + 1;
+ }
+}
+
+@rTime = ();
+@wTime = ();
+@rMBs = ();
+@wMBs = ();
+@ACTIVEOST_INX;
+
+# Locals
+my $filename = "";
+my $dirpath = "";
+my $flag = 0;
+
+# Command line parameter parsing
+use Getopt::Std;
+getopts('s:h') or usage();
+usage() if $opt_h;
+$FSIZE = $opt_s if $opt_s;
+
+my $i = 0;
+foreach (@ARGV) {
+ $MNT = $_;
+ $i++;
+ if ($i > 1) {
+ print "ERROR: extra argument $_\n";
+ usage();
+ }
+}
+#Check for Time::HiRes module
+my $CheckTimeHiRes = "require Time::HiRes";
+eval ($CheckTimeHiRes) or die "You need to install the perl-Time-HiRes package to use this script\n";
+
+use POSIX qw(strftime);
+my $time_v = time();
+my $hostname = `lctl list_nids | head -1` or die "You need to install lctl to use this script\n";
+chop($hostname);
+print "$pname: ", strftime("%D", localtime($time_v));
+print " OST speed survey on $MNT from $hostname\n";
+
+# get OST count
+ost_count ();
+
+use File::Path;
+while ($CNT < $OSTS) {
+ $dirpath = "$MNT/tmpdir$CNT";
+ eval { mkpath($dirpath) };
+ if ($@) {
+ print "Couldn't create $dirpath: $@";
+ exit 1;
+ }
+ $filename = "$dirpath/file$CNT";
+ if ( $ACTIVEOST_INX[$CNT] ) {
+ # set stripe for OST number $CNT
+ system ("lfs setstripe $filename 0 $CNT 1");
+ # Perform write for OST number $CNT
+ &run_test($FSIZE,$CNT,"write",$filename);
+ # Perform read for OST number $CNT
+ &run_test($FSIZE,$CNT,"read",$filename);
+ $flag++;
+ }
+ eval { rmtree($dirpath) };
+ if ($@) {
+ print "Warning: Couldn't $dirpath: $@";
+ }
+ $CNT = $CNT + 1;
+}
+# if read or write performed on any OST then display information.
+if ( $flag ) {
+ if ( $flag > 1 ) {
+ &calculate("Read",@rMBs);
+ &calculate("Write",@wMBs);
+ }
+ output_all_data ();
+} else {
+ print "There is no active OST's found\n";
+}