#!/bin/bash
# kernbench by Con Kolivas <kernbench@kolivas.org>
# based on a benchmark by Martin J. Bligh
trap 'echo "ABORTING";exit' 1 2 15

VERSION=0.42

num_runs=5
single_runs=0
half_runs=1
opti_runs=1
max_runs=1
fast_run=0

while getopts vsHOMn:o:hf i
do
	case $i in
		 h) 	echo "kernbench v$VERSION by Con Kolivas <kernbench@kolivas.org>"
				echo "Usage:"
				echo "kernbench [-n runs] [-o jobs] [-s] [-H] [-O] [-M] [-h] [-v]"
				echo "n : number of times to perform benchmark (default 5)"
				echo "o : number of jobs for optimal run (default 4 * cpu)"
				echo "s : perform single threaded runs (default don't)"
				echo "H : don't perform half load runs (default do)"
				echo "O : don't perform optimal load runs (default do)"
				echo "M : don't perform maximal load runs (default do)"
				echo "f : fast run"
				echo "h : print this help"
				echo "v : print version number"
				exit ;;
		 v) echo "kernbench Version $VERSION by Con Kolivas <kernbench@kolivas.org>" ; exit ;;
		 n) nruns=$OPTARG ;;
		 o) optijobs=$OPTARG ;;
		 s) single_runs=1 ;;
		 H) half_runs=0 ;;
		 O) opti_runs=0 ;;
		 M) max_runs=0 ;;
		 f) fast_run=1 ;;
	esac
done

if [[ ! -f include/linux/kernel.h ]] ; then
	echo "No kernel source found; exiting"
	exit
fi

for i in time awk yes date
do
	iname=`which $i`
	if [[ ! -a $iname ]] ; then
		echo "$i not found in path, please install it; exiting"
		exit
	fi
done

time=`which time`

if [[ $nruns -gt 0 ]] ; then
	num_runs=$nruns
elif [[ $fast_run -eq 1 ]]; then
	echo "Dropping to 3 runs for fast run"
	num_runs=3
fi

if (($num_runs < 1)) ; then
	echo "Nothing to do; exiting"
	exit
fi

if (($num_runs > 10)) ; then
	echo "Are you crazy? trimming number of runs to 10"
	num_runs=10
fi

if [[ ! -d /proc ]] ; then
	echo "Can't find proc filesystem; exiting"
	exit
fi

mem=`awk '/MemTotal/ {print $2}' /proc/meminfo`
if [[ $mem -lt 4000000 && $max_runs -gt 0 ]] ; then
	echo Less than 4Gb ram detected!
	echo Maximal loads will not measure cpu throughput and may cause a swapstorm!
	echo If you did not plan this, -M flag is recommended to bypass maximal load.
fi

(( single_runs *= $num_runs ))
(( half_runs *= $num_runs ))
(( opti_runs *= $num_runs ))
(( max_runs *= $num_runs ))

cpus=`grep -c ^processor /proc/cpuinfo`
echo $cpus cpus found
echo Cleaning source tree...
make clean > /dev/null 2>&1

if [[ $fast_run -eq 0 ]] ; then
	echo Caching kernel source in ram...
	for i in `find -type f`
	do
		cat $i > /dev/null
	done
fi

if [[ ! -f .config ]] ; then
	echo No old config found, using defconfig
	echo Making mrproper
	make mrproper > /dev/null 2>&1
	echo Making defconfig...
	make defconfig > /dev/null 2>&1
else
	echo Making oldconfig...
	yes "" | make oldconfig > /dev/null 2>&1
fi

halfjobs=$(( $cpus / 2 ))
optijobs=${optijobs:=$(( $cpus * 4 ))}

if [[ $halfjobs -lt 2 ]] ; then
	echo "Half load is no greater than single; disabling"
	half_runs=0
elif [[ $halfjobs -eq 2 ]] ; then
	echo "Half load is 2 jobs, changing to 3 as a kernel compile won't guarantee 2 jobs"
	halfjobs=3
fi

echo Kernel `uname -r`
echo Performing $num_runs runs of
if [[ $single_runs -gt 0 ]] ; then
	echo make
fi
if [[ $half_runs -gt 0 ]] ; then
	echo make -j $halfjobs
fi
if [[ $opti_runs -gt 0 ]] ; then
	echo make -j $optijobs
fi
if [[ $max_runs -gt 0 ]] ; then
	echo make -j
fi
echo

echo All data logged to kernbench.log

if [[ $fast_run -eq 0 ]] ; then
	echo Warmup run...
	make -j $optijobs > /dev/null 2>&1
fi

date >> kernbench.log
uname -r >> kernbench.log

add_data_point()
{
    echo $@ | awk '{printf "%.6f %.6f %d", $1 + $2, $1 * $1 + $3, $4 + 1}'
}

show_statistics()
{
    case $3 in
	0)
	    echo "No data"
	    ;;
	1)
	    echo $1
	    ;;
	*)
	    avg=`echo $1 $3 | awk '{print $1 / $2}'`
	    var=`echo $1 $2 $3 | awk '{print ($2 - ($1 * $1) / $3) / ($3 - 1)}'`
	    sdev=`echo $var | awk '{print $1^0.5}'`
	    echo "$avg ($sdev)"
	    ;;
    esac
}

do_log()
{
	echo "Average $runname Run (std deviation):" > templog
	echo Elapsed Time  `show_statistics $temp_elapsed` >> templog
	echo User Time  `show_statistics $temp_user` >> templog
	echo System Time  `show_statistics $temp_sys` >> templog
	echo Percent CPU  `show_statistics $temp_percent` >> templog
	echo Context Switches  `show_statistics $temp_ctx` >> templog
	echo Sleeps  `show_statistics $temp_sleeps` >> templog
	echo >> templog
	cat templog
	cat templog >> kernbench.log
}

do_runs()
{
	temp_elapsed="a"
	for (( i=1 ; i <= temp_runs ; i++ ))
	do
		echo $runname run number $i...
		make clean > /dev/null 2>&1
		sync
		if [[ $fast_run -eq 0 ]] ; then
			sleep 5
		fi
		$time -f "%e %U %S %P %c %w" -o timelog make -j $tempjobs > /dev/null 2>&1
		read elapsed_time user_time sys_time percent ctx sleeps <timelog
		temp_elapsed=`add_data_point $elapsed_time $temp_elapsed`
		temp_user=`add_data_point $user_time $temp_user`
		temp_sys=`add_data_point $sys_time $temp_sys`
		temp_percent=`add_data_point $percent $temp_percent`
		temp_ctx=`add_data_point $ctx $temp_ctx`
		temp_sleeps=`add_data_point $sleeps $temp_sleeps`
	done
	if [[ $temp_runs -ne 0 ]] ; then
		do_log
	fi
}

temp_runs=$single_runs
tempjobs=1
runname="Single threaded"
do_runs

temp_runs=$half_runs
tempjobs=$halfjobs
runname="Half load -j $halfjobs"
do_runs

temp_runs=$opti_runs
tempjobs=$optijobs
runname="Optimal load -j $optijobs"
do_runs

temp_runs=$max_runs
tempjobs=""
runname="Maximal load -j"
do_runs