#!/bin/bash
# Regression tests for tuna
# (c) 2008 Red Hat Inc.
# Arnaldo Carvalho de Melo <acme@redhat.com>
# Released under the GPLv2

dprint() {
	[ -n "$VERBOSE" ] && echo $1
}

ktpidof() {
	echo $(ps ax -To pid,cmd | grep "\[$1.*\]" | head -1 | cut -d'[' -f 1)
}

get_rtprio() {
	echo $(chrt -p $1 | grep priority | cut -d ':' -f 2)
}

get_policy() {
	echo $(chrt -p $1 | grep policy | cut -d ':' -f 2)
}

get_affinity() {
	echo $(taskset -p $1 | grep 'current affinity' | cut -d ':' -f 2)
}

get_nr_processors() {
	echo $(grep -i "^processor.*: " /proc/cpuinfo | tail -1 | cut -d ':' -f 2)
}

get_nr_cpu_sockets() {
	echo $(grep "^physical id" /proc/cpuinfo | cut -d: -f2 | sort -u | wc -l)
}

die() {
	[ -z "$VERBOSE" ] && echo -n "$2: "
	echo $1
	rtctl --file $INITIAL reset
	taskset -p $INITIAL_INIT_AFFINITY 1 > /dev/null
	rm -rf $TESTUNA_DIR
	exit 1
}

die_with_a_diff() {
	[ -n "$VERBOSE" ] && diff -u $INITIAL $NEW
	die "$1"
}

tuna_save() {
	tuna --save $TEMPCONF
	grep -v '^# rtctl --file ' $TEMPCONF > $1
	rm -f $TEMPCONF
}

die_if_not_saved() {
	dprint "$2"
	tuna_save $NEW
	(diff -u $INITIAL $NEW | diffstat | \
	grep -q "[ \t]*1 file changed, $1 insertions*(+), $1 deletions*(-)") ||
	die_with_a_diff 'FAILED!' "$2"
}

die_if_conf_changed() {
	dprint "$1"
	tuna_save $NEW
	diff -qu $INITIAL $NEW > /dev/null || die_with_a_diff 'FAILED!' "$1":
}

die_if_zero() {
	dprint "$2"
	[ $1 -eq 0 ] && die 'FAILED!' "$2"
}

die_if_not_zero() {
	dprint "$2"
	[ $1 -ne 0 ] && die 'FAILED!' "$2"
}

die_if_not_equal() {
	dprint "$3"
	[ $1 -ne $2 ] && die 'FAILED!' "$3"
}

die_if_not_str_equal() {
	dprint "$3"
	[ $1 != $2 ] && die 'FAILED!' "$3"
}

TESTUNA_DIR=$(mktemp -d -t testuna.XXXXXX) || exit 1
INITIAL=$TESTUNA_DIR/initial.tuna
INITIAL_INIT_AFFINITY=$(get_affinity 1)
TEMPCONF=$TESTUNA_DIR/tempnew.tuna
NEW=$TESTUNA_DIR/new.tuna

[ $# -eq 1 ] && VERBOSE=1

dprint "Saving initial configuration"

tuna_save $INITIAL

TUNA_RPM_VERSION=$(rpm -q --qf "%{version}\n" tuna)
TUNA_BIN_VERSION=$(tuna --version)
die_if_not_str_equal "$TUNA_RPM_VERSION" "$TUNA_BIN_VERSION" \
	"Verifying --version ($TUNA_BIN_VERSION) matches package version ($TUNA_RPM_VERSION)"

rtctl --file $INITIAL reset

die_if_conf_changed "Replaying initial config"

PID=$(ktpidof "watchdog")
RTPRIO=$(get_rtprio $PID)
POLICY=$(get_policy $PID)
POLICY=$(echo ${POLICY:6:1} | tr 'A-Z' 'a-z')
chrt -$POLICY -p $((RTPRIO - 1)) $PID 

die_if_not_saved 1 'Saving changes to a kernel thread priority'

chrt -$POLICY -p $RTPRIO $PID 

die_if_conf_changed 'Restoring kernel thread priority'

new_policy=$(echo $POLICY | tr fr rf)

chrt -$new_policy -p $RTPRIO $PID 

die_if_not_saved 1 'Changing kernel thread sched policy'

chrt -$POLICY -p $RTPRIO $PID 

die_if_conf_changed 'Restoring kernel thread sched policy'

PID=$(ktpidof "kthreadd")
AFFINITY=$(get_affinity $PID)

taskset -p 0x2 $PID > /dev/null

die_if_not_saved 1 'Changing kernel thread SMP affinity mask'

taskset -p $AFFINITY $PID > /dev/null

die_if_conf_changed 'Restoring kernel thread SMP affinity mask'

NR_PROCESSORS=$(get_nr_processors)
for PROCESSOR in $(seq 0 $NR_PROCESSORS) ; do
	taskset -p 0xff 1 > /dev/null

	PROCESSOR_AFFINITY=$(printf "%#x\n" $((1 << PROCESSOR)))
	tuna --cpu $PROCESSOR --isolate

	AFFINITY=0x$(get_affinity 1)

	die_if_not_zero $((AFFINITY & PROCESSOR_AFFINITY)) "Isolating CPU $PROCESSOR"

	tuna --cpu $PROCESSOR --include

	AFFINITY=0x$(get_affinity 1)

	die_if_zero $((AFFINITY & PROCESSOR_AFFINITY)) "Including CPU $PROCESSOR"
done

NEW_AFFINITY=$((1 << NR_PROCESSORS | 1))

if [ $NR_PROCESSORS -gt 2 ]; then
	tuna --cpu=0,$NR_PROCESSORS --isolate

	for PID in $(cd /proc; ls -d [0-9]*) ; do
		[ -n "$(cat /proc/$PID/cmdline 2> /dev/null)" ] || continue
		AFFINITY=0x$(get_affinity $PID) || continue

		die_if_not_zero $((AFFINITY & NEW_AFFINITY)) \
			"Verifying isolation of first and last processor for PID $PID"
	done
fi

tuna --cpu=0,$NR_PROCESSORS --include

AFFINITY=0x$(get_affinity 1)

die_if_not_equal $((AFFINITY & NEW_AFFINITY)) $NEW_AFFINITY "Including first and last processor"

taskset -p 0xff 1 > /dev/null

NEW_AFFINITY=$((1 << NR_PROCESSORS | 1))

tuna --cpu=0,$NR_PROCESSORS --thread 1 --move

AFFINITY=0x$(get_affinity 1)

die_if_not_equal $((AFFINITY & NEW_AFFINITY)) $NEW_AFFINITY "Moving init to just first and last processor"

NR_CPU_SOCKETS=$(get_nr_cpu_sockets)
if [ $NR_CPU_SOCKETS -ge 2 ]; then
	CPU1_SIBLINGS=$(printf "%d" 0x$(cat /sys/devices/system/cpu/cpu1/topology/core_siblings | cut -d',' -f2))
	CPU1_SOCKET=$(cat /sys/devices/system/cpu/cpu1/topology/physical_package_id)

	tuna --sockets=$CPU1_SOCKET --isolate
	AFFINITY=0x$(get_affinity 1)
	die_if_not_zero $((AFFINITY & CPU1_SIBLINGS)) \
		"Verifying isolation of socket $CPU1_SOCKET"

	tuna --sockets=$CPU1_SOCKET --include
	AFFINITY=0x$(get_affinity 1)
	die_if_not_equal $((AFFINITY & CPU1_SIBLINGS)) $CPU1_SIBLINGS \
		"Verifying inclusion of socket $CPU1_SOCKET"

	tuna --sockets=$CPU1_SOCKET --isolate
	tuna --sockets=$CPU1_SOCKET --thread 1 --move
	AFFINITY=0x$(get_affinity 1)
	die_if_not_equal $((AFFINITY & CPU1_SIBLINGS)) $CPU1_SIBLINGS "Moving init to CPU socket $CPU1_SOCKET"
fi

if [ $NR_PROCESSORS -gt 2 ]; then
	THREAD_PREFIX="watchdog/"
	PID=$(ps h -C ${THREAD_PREFIX}0 -o pid)
	RTPRIO=$(get_rtprio $PID)
	NEW_RTPRIO=$((RTPRIO - 1))
	tuna -t $THREAD_PREFIX* -p $NEW_RTPRIO
	for CPU in $(seq 0 $((NR_PROCESSORS - 1))); do
		PID=$(ps h -C ${THREAD_PREFIX}$CPU -o pid)
		RTPRIO=$(get_rtprio $PID)
		die_if_not_equal $RTPRIO $NEW_RTPRIO "Using --thread globbing"
	done
fi

taskset -p $INITIAL_INIT_AFFINITY 1 > /dev/null
rtctl --file $INITIAL reset

echo 'PASS: Healthy tuna, no lead found, eat!'

exit 0
