#!/bin/bash
# simple regression test for numactl/numaapi
# must be run from 'test' directory of numactl source package,
# after build [just use 'make test']
# note the statistics checks may fail when the system is under
# memory pressure
# Copyright 2003,2004 Andi Kleen, SuSE Labs.

testdir=`dirname "$0"`
: ${srcdir:=${testdir}/..}
: ${builddir:=${srcdir}}
export PATH=${builddir}:$PATH

: ${NUMACTL:=${builddir}/numactl}
VALGRIND=${VALGRIND:-}

MB=$[1024*1024]
SIZE=$[15 * $MB]
DEMOSIZE=$[10 * $MB]
STAT_INTERVAL=5

PAGESIZE=$("${builddir}/test/pagesize")
PAGES=$[ $SIZE / $PAGESIZE ]
HALFPAGES=$[ $PAGES / 2 ]
HALFPAGES=$[ $HALFPAGES - 100 ]
DOUBLEPAGES=$[ $PAGES * 2 ]
DOUBLEPAGES=$[ $DOUBLEPAGES - 200 ]
NEEDPAGES=$[ $DOUBLEPAGES + $DOUBLEPAGES / 5 ] # 20% spare

EXIT=0

declare -i maxnode
declare -a node
declare -a nlist

# =====================================================================
numactl() {
	$VALGRIND $NUMACTL "$@"
}

failed() {
    echo '=======FAILED'
    echo "Check if machine doesn't have background jobs and try again"
    EXIT=1
}

# nstat statname node
nstat() {
    sleep $STAT_INTERVAL
    nid=node$2
    id=`numastat | head -1 | awk -v node=$nid '{ for (i = 1; i <= NF; ++i) if($i==node) print i; exit }'`
    declare -a fields
    numastat | grep $1 | while read -a fields ; do
        echo ${fields[$id]}
    done
}

probe_hardware()
{
	declare -i n=0

	numnodes=$(numactl --hardware | awk '/^available/ { print $2 }')
	maxnode=$(expr $numnodes - 1)
	nlist=( $(numactl --hardware | grep "^node" |  tail -1 |awk '{$1=""; print }') )

	# find nodes with at least NEEDPAGES of free memory
	for i in $(seq 0 $maxnode) ; do
		free=$(numactl --hardware | fgrep " ${nlist[$i]} free" | awk '{print $4}')
		free=$(( free * MB ))
		if [[ $((free / PAGESIZE)) -ge $NEEDPAGES ]]; then
			node[$n]=${nlist[$i]}
			n=$((n + 1 ))
		fi
	done
	numnodes=$n
	maxnode=$(expr $numnodes - 1)

	if [ $numnodes -lt 2 ] ; then
	    echo "need at least two nodes with at least $NEEDPAGES each of"
	    echo "free memory for mempolicy regression tests"
	    exit 77  # Skip test
	fi
}

# =========================================================================
_test_process_state() {
    echo '=>testing numactl' "$@" "memhog -H $SIZE"
    numactl "$@" memhog -H $SIZE  || failed
}

test_process_state()
{
	declare -i n0=${node[0]} n1=${node[1]}
	_test_process_state --interleave=$n1

	a0=`nstat interleave_hit $n0`
	a1=`nstat interleave_hit $n1`
	_test_process_state --interleave=$n0,$n1
	b0=`nstat interleave_hit $n0`
	b1=`nstat interleave_hit $n1`
	if [ $(expr $b1 - $a1) -lt $HALFPAGES ]; then
	    echo "interleaving test failed $n1 $b1 $a1"
	    failed
	fi
	if [ $(expr $b0 - $a0) -lt $HALFPAGES ]; then
	    echo "interleaving test failed $n0 $b0 $a0"
	    failed
	fi

	_test_process_state --interleave=all
	_test_process_state --membind=all

	a=$(expr $(nstat numa_hit $n0) + $(nstat numa_hit $n1))
	_test_process_state --membind=$n0,$n1
	b=$(expr $(nstat numa_hit $n0) + $(nstat numa_hit $n1))
	if [ $(expr $b - $a) -lt $PAGES ]; then
	    echo "membind test failed $n1 $b $a ($PAGES)"
	    failed
	fi

	for i in "${node[@]}" ; do
		a=`nstat numa_hit $i`
		_test_process_state --membind=$i
		_test_process_state --preferred=$i
		b=`nstat numa_hit $i`
		if [ $(expr $b - $a) -lt $DOUBLEPAGES ]; then
		    echo "membind/preferred on node $ni failed $b $a"
		    failed
		fi
	done
	_test_process_state --localalloc
}

# =========================================================================
# test mbind

_test_mbind() {
	echo '=>testing memhog -H' "$@"
	memhog -H $SIZE "$@" || failed
}

test_mbind()
{
	declare -i n0=${node[0]} n1=${node[1]}

	a0=`nstat interleave_hit $n0`
	a1=`nstat interleave_hit $n1`
	_test_mbind interleave $n0,$n1
	b0=`nstat interleave_hit $n0`
	b1=`nstat interleave_hit $n1`
	if [ $(expr $b1 - $a1) -lt $HALFPAGES ]; then
	    echo "interleaving test 2 failed $n1 $b1 $a1 expected $HALFPAGES"
	    failed
	fi
	if [ $(expr $b0 - $a0) -lt $HALFPAGES ]; then
	    echo "interleaving test 2 failed $n0 $b0 $a0"
	    failed
	fi

	_test_mbind interleave all

	a=$(expr $(nstat numa_hit $n0) + $(nstat numa_hit $n1))
	_test_mbind membind $n0,$n1
	b=$(expr $(nstat numa_hit $n0) + $(nstat numa_hit $n1))
	if [ $(expr $b - $a) -lt $PAGES ]; then
	    echo "membind test 2 failed $b $a ($PAGES)"
	    failed
	fi

	for i in "${node[@]}" ; do
		declare -i ni=${node[$i]}
		a=`nstat numa_hit $i`
		_test_mbind membind $i
		_test_mbind preferred $i
		b=`nstat numa_hit $i`
		if [ $(expr $b - $a) -lt $DOUBLEPAGES ]; then
		    echo "membind/preferred test 2 on node $ni failed $b $a"
		    failed
		fi
	done
}

# =========================================================================
main()
{

	# Get the interval vm statistics refresh at
	if [ -e /proc/sys/vm/stat_interval ]; then
		STAT_INTERVAL=`cat /proc/sys/vm/stat_interval`
		STAT_INTERVAL=`expr $STAT_INTERVAL \* 2`
	fi

	probe_hardware

	numactl --cpubind=${node[0]} /bin/true
	numactl --cpubind=${node[1]} /bin/true

	numactl -s
	numactl --hardware

	numastat > A

	test_process_state

	test_mbind

	numastat > B
	diff -u A B
	rm A B

	if [ "$EXIT" = 0 ] ; then
		echo '========SUCCESS'
	else
		echo '========FAILURE'
		exit 1
	fi
}

# =========================================================================
main
