#!/bin/bash 
# iotest v0.1 - lawalsh: open usage allowed
# 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 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

##
# Defaults for bs count and iosize
##

int bs count
: ${bs:=16*M}  ${count:=64}
#if ((${#bs})); then bs=$bs; fi
#if ((${#count})); then count=$count; fi

int iosize=$count*$bs


#	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 
		printf >&2 'hdisp: parm is not num: "%s"\n' "$parm"
	fi
	int num=${1:?}; shift
	my buf="$(perl -e 'use warnings; use strict;
	my $sfxs = [ " ", qw( K M G T P E ) ];
	sub human($;$) {
		my $value = shift; my $scale = @_ ? $_[0]: 1024;
		my $i;
		for ($i=0; $value >= 999.5 && $i < @$sfxs; ++$i) { $value/=$scale }
		sprintf	$value==0 ? "0":
						$value<99.5 ? "%.1f%s"
												: "%.0f%s", $value, $sfxs->[$i];
	}
	printf "%s\n", human($ARGV[0]);' "$num")"
	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)

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

printf "Using bs=%s, count=%s, iosize=%s\n" "$bsbuf" "$count" "$ios_buf"


onecycle
