#!/bin/bash
# iotest v0.2 - lawalsh(at)tlinx.org : open usage allowed
# (c) 2014-2018
# vim=:SetNumberAndWidth
_prgpth="${0:?}"; _prg="${_prgpth##*/}"; _prgdr="${_prgpth%/$_prg}"
[[ -z $_prgdr || $_prg == $_prgdr ]] && $_prgdr="$PWD"
export PATH="$_prgdr:$_prgdr/lib:$PATH"
shopt -s expand_aliases extglob sourcepath ; set -o pipefail
#include stdalias
#include Types
Dd=$(type -P dd)
[[ $Dd ]] || { echo "Cannot find dd. Cannot proceed."; exit 1; }
alias my=declare sub=function int='my -i' array='my -a'
alias map='my -A' intConst='int -x' string=my
# 1 num = block size
# num-num = range of block sizes to test; w/increment = "2x", so
# 4M-16M tests 4M, 8M, 16M
# 4M-12M test 4M, 8M, 12M
# count adjusted to xfer 4G, rounding up
#----
#all xfers are using 'devices' (/dev/zero for source, /dev/null for target)
# remote filenames "zero" and "null" should be setup to be remote devices
intConst K=1024 M=K*K G=M*K T=G*K
my sfxs="KMGT"
##
# Defaults for BS count and IOSIZE
##
check_parm () {
my param=$1
if [[ ${!param} =~ ^[0-9]+[$sfxs]$ ]]; then
my unit=${!param:0-1}
eval $param="${!param:0:0-1}*$unit"
elif [[ ${!param} =~ ^[0-9]+$ ]]; then
: # ok as is
elif [[ ! ${!param} =~ ^[0-9]+\*[$sfxs]$ ]]; then
printf >&2 "Invalid number format \"%s\", for \"%s\"\n" "${!param}" "$param"
exit 1
fi
}
my BS COUNT IOSIZE
for p in BS COUNT IOSIZE; do
if [[ ${!p} ]]; then
check_parm $p
int $p=${!p}
echo "${p}=${!p}"
fi
done
int params=0
array -i coef=(0 0 0)
#: ${BS:=16*M} ${COUNT:=64}
if ((${#BS})); then int BS=$BS; params+=1; coef[0]=1; fi
if ((${#COUNT})); then int COUNT=$COUNT; params+=1; coef[1]=1; fi
if ((${#IOSIZE})); then int IOSIZE=$IOSIZE; params+=1 coef[2]=1; fi
int BS=${BS:-16*M}
int COUNT=${COUNT:-64}
int IOSIZE=${IOSIZE:-2*G}
if ((params>=3)); then
printf >&2 "Error: can only specify 2 params; 3rd is calculated\n"; exit 1;
elif ((params==2)); then
if ((!coef[2])); then int IOSIZE=COUNT*BS
elif ((!coef[1])); then int COUNT=IOSIZE/BS
elif ((!coef[0])); then int BS=IOSIZE/COUNT
fi
elif ((params==1)); then
if ((coef[2])) ; then # only size spec'd, use default for BS unless too big
if ((BS>IOSIZE)); then BS=IOSIZE/COUNT
else COUNT=IOSIZE/BS
fi
elif ((coef[1])); then BS=IOSIZE/COUNT
else COUNT=IOSIZE/BS
fi
fi
int IOSIZE=COUNT*BS
echo "BS=$BS, COUNT=$COUNT, IOSIZE=$IOSIZE"
# desuffix arg1 [arg2]
# desuffix arg1 - num + suffix -> convert to int
# arg2 - optional buff name (else print to stdout)
# return 0 if no error
desuffix () { #convert num+Suff => int store in optional Buff
my str="${1:?}" ; shift
my bufnam=""; (($#)) && bufnam=$1
if [[ $str =~ ^([0-9]+)([KMGT])$ ]]; then
int num=${BASH_REMATCH[1]}*${BASH_REMATCH[2]}
((num)) || return 1
if [[ $bufnam ]] ; then printf -v $bufnam "%d" "$num"
else printf "%d" "$num" ; fi
else
return $p
fi
}
hdisp () {
my parm=${1:?}
if [[ ! $parm =~ ^[0-9]+$ ]]; then echo >&2 "hdisp: parm1 is not num: \"$parm\""; fi
int num=${1:?}; shift
export PERL5OPT="$PERL5OPT -I/h/bin/lib"
my buf="$(perl -e 'use Hout; use P; P "%s", human($ARGV[0]);' "$num")"
#printf "hdisp-buf=%s\n" "$buf"
string bufnam=""; (($#)) && bufnam=$1
if [[ $bufnam ]] ; then printf -v $bufnam "%s" "$buf"
else printf "%s" "$buf" ; fi
}
map args=([b]=setblocksize [i]=iosize )
setblocksize () { BS=$1 ; }
iosize () { IOSIZE=$1 ; }
check_params () {
int num=0
while (($#)) ; do
my arg=$1 ; shift
if [[ ${arg:0:1} == - ]]; then arg=${arg:1}; fi
my switch=${args["$arg"]:-""}
int iswitch=0
if [[ ${switch:-""} ]]; then
my val=$1; shift
my nval=""
int ival=0
desuffix "$val" nval
if [[ $nval ]]; then ival=0+nval; fi
$switch $ival
fi
done
COUNT=IOSIZE/BS
}
(($#)) && check_params "$@"
string testdir=/h
array reada=($testdir/zero /dev/null )
array writea=(/dev/zero $testdir/null oflag=direct conv=nocreat,notrunc)
sub dd_io {
local if="$1" of="$2"; shift 2
nice --19 $Dd if="$if" of="$of" bs="$BS" count="$COUNT" iflag=fullblock\
conv=nocreat "$@"
}
dd () {
local if="$1" of="$2" ; parms="$3"; shift 2
array out=()
my ignore_RE='^[^ \t]+ records\ (in|out)$'
readarray out < <( dd_io "$if" "$of" "$@" |& { int s=$?
if ((s)); then echo >&2 "stat:$s"; else cat; fi ; }
)
printf "%s\n" "${out[@]}"
return 0
}
dd_format () {
my bytes btxt pnum1 suffp1 pnum2 suffp2 copt time rest
while read bytes btxt pnum1 suffp1 pnum2 suffp2 copt time rest; do
if ! [[ $time =~ ^[0-9] ]]; then
copt=$pnum2; time=$suffp2;
fi
[[ $btxt == records ]] && continue
[[ $bytes && $time ]] || continue
my sizeb="" rateb=""
my bps="$(echo -E "$bytes/$time" | bc)"
hdisp "$bytes" sizeb
hdisp "$bps" rateb
my fmt="%d bytes (%sB) copied, %s s, %sB/s\n"
printf "$fmt" "$bytes" "$sizeb" "$time" "$rateb"
done
}
onecycle () {
echo -n "R:"; { dd "${reada[@]}" || exit $?; } | dd_format
sync;sleep .1;sync
echo -n "W:"; { dd "${writea[@]}" || exit $?; } | dd_format
}
my bsbuf="" ios_buf=""
hdisp "$BS" bsbuf
hdisp "$IOSIZE" ios_buf
#if (($#)) ; then
# if [[ $1 == -h || $1 == -\? ]]; then
# printf "Defaults: BS=16M, COUNT=%d, IOSIZE=%s" $Count $IOSize
# printf "BlkSize, cnt & Total can be overriden with BS, COUNT & IOSIZE:\n"
# printf "BS=16M (uses 1G as Total IOSIZE), or\n" "$_prg" :
# printf "BS=1M COUNT=1K %s (uses 1G as Total IOSIZE), or\n" "$_prg"
# printf ""
# fi
#fi
printf "Using bs=%s, count=%s, iosize=%s\n" "$bsbuf" "$COUNT" "$ios_buf"
onecycle