Blob Blame History Raw
# Copyright (C) 2013  Miroslav Lichvar <mlichvar@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

export LC_ALL=C
export PATH=$(pwd):$PATH

if [ ! -e clknetsim ]; then
	git clone https://github.com/mlichvar/clknetsim.git || exit 1
fi

if [ ! -x clknetsim/clknetsim -o ! -e clknetsim/clknetsim.so ]; then
	pushd clknetsim
	make || exit 1
	popd
fi

export CLKNETSIM_PATH=clknetsim

. $CLKNETSIM_PATH/clknetsim.bash

default_limit=500
default_time_offset=1e-1
default_freq_offset=1e-4
default_base_delay=1e-7
default_jitter=1e-7
default_wander=1e-9
default_time_rms_limit=1e-6
default_freq_rms_limit=1e-6
default_time_max_limit=2e-6
default_freq_max_limit=2e-6
default_min_sync_time=10
default_max_sync_time=40
default_max_ptp4l_delay_limit=1e-4
default_nodes=3
default_subnets=""
default_master_node=1
default_transparent_nodes=""
default_free_running_nodes=""
default_pmc_node=0
default_nsm_node=0
default_master_start=0.0
default_slave_start=9.0
default_pmc_start=50.0
default_nsm_start=50.0
default_master_step=""
default_slave_step=""
default_update_interval=0
default_log_packets=0
default_master_conf=""
default_slave_conf=""
default_transparent_conf=""
default_pmc_conf=""
default_nsm_conf=""
default_extra_ptp4l_options=""

for defopt in $(declare | grep '^default_'); do
	defoptname=${defopt%%=*}
	optname=${defoptname#default_}
	eval "[ -z \"\${$optname:+a}\" ] && $optname=\"\$$defoptname\""
done

test_start() {
	rm -f tmp/*
	echo "Testing $@:"
}

test_pass() {
	echo "PASS"
	exit 0
}

test_fail() {
	echo "FAIL"
	exit 1
}

test_ok() {
	pad_line
	echo -e "\tOK"
	return 0
}

test_bad() {
	pad_line
	echo -e "\tBAD"
	return 1
}

test_error() {
	pad_line
	echo -e "\tERROR"
	return 1
}

msg_length=0
pad_line() {
	local line_length=56
	[ $msg_length -lt $line_length ] && \
		printf "%$[$line_length - $msg_length]s" ""
	msg_length=0
}

test_message() {
	local level=$1 eol=$2
	shift 2
	local msg="$*"

	while [ $level -gt 0 ]; do
		echo -n "  "
		level=$[$level - 1]
		msg_length=$[$msg_length + 2]
	done
	echo -n "$msg"

	msg_length=$[$msg_length + ${#msg}]
	if [ $eol -ne 0 ]; then
		echo
		msg_length=0
	fi
}

check_sync() {
	local i sync_time max_time_error max_freq_error ret=0
	local rms_time_error rms_freq_error

	test_message 2 1 "checking clock sync time, max/rms time/freq error:"
	
	for i in $(seq 1 $nodes); do
		[ $i -eq $master_node ] && continue
		[ $i -eq $pmc_node ] && continue
		[ $i -eq $nsm_node ] && continue
		[[ " $free_running_nodes " =~ [^0-9]$i[^0-9] ]] && continue

		sync_time=$(find_sync tmp/log.offset tmp/log.freq $i \
			$time_max_limit $freq_max_limit 1.0)
		max_time_error=$(get_stat 'Maximum absolute offset' $i)
		max_freq_error=$(get_stat 'Maximum absolute frequency' $i)
		rms_time_error=$(get_stat 'RMS offset' $i)
		rms_freq_error=$(get_stat 'RMS frequency' $i)

		test_message 3 0 "node $i: $sync_time $(printf '%.2e %.2e %.2e %.2e' \
			$max_time_error $max_freq_error $rms_time_error $rms_freq_error)"

		check_stat $sync_time $min_sync_time $max_sync_time && \
			check_stat $max_time_error 0.0 $time_max_limit && \
			check_stat $max_freq_error 0.0 $freq_max_limit && \
			check_stat $rms_time_error 0.0 $time_rms_limit && \
			check_stat $rms_freq_error 0.0 $freq_rms_limit && \
			test_ok || test_bad

		[ $? -eq 0 ] || ret=1
	done

	return $ret
}

check_ptp4l_delay() {
	local i max_delay ret=0

	test_message 2 1 "checking max abs path delay measured by ptp4l:"

	for i in $(seq 1 $nodes); do
		[ $i -eq $master_node ] && continue
		[ $i -eq $pmc_node ] && continue
		[ $i -eq $nsm_node ] && continue

		max_delay=$(grep -oE 'path delay *[-0-9]+ +[-0-9]+$' tmp/log.$i | awk '
			BEGIN {
				max = 0.0
			} { 
				abs = $4 < 0 ? -$4 : $4
				if (max < abs)
					max = abs
			} END {
				print max / 1e9
			}')

		test_message 3 0 "node $i: $(printf '%.2e' $max_delay)"

		check_stat $max_delay 0.0 $max_ptp4l_delay_limit && test_ok || test_bad

		[ $? -eq 0 ] || ret=1
	done

	return $ret
}
	
check_pmc_output() {
	local pattern=$1
	test_message 2 0 "checking pmc output:"
	[[ "$(cat tmp/log.$pmc_node)" =~ $pattern ]] && test_ok || test_bad
}

check_nsm_output() {
	local pattern=$1
	test_message 2 0 "checking nsm output:"
	[[ "$(cat tmp/log.$nsm_node)" =~ $pattern ]] && test_ok || test_bad
}

print_nondefaults() {
	local defopt defoptname optname

	test_message 2 1 "non-default settings:"
	declare | grep '^default_*' | while read defopt; do
		defoptname=${defopt%%=*}
		optname=${defoptname#default_}
		eval "[ \"\$$optname\" = \"\$$defoptname\" ]" || \
			test_message 3 1 $(eval "echo $optname=\$$optname")
	done
}

get_wander_expr() {
	local scaled_wander

	scaled_wander=$(awk "BEGIN {print $wander / \
		sqrt($update_interval < 0 ? 2^-($update_interval) : 1)}")

	echo "(+ $freq_offset (sum (* $scaled_wander (normal))))"
}

run_simulation() {
	test_message 2 0 "running simulation:"

	start_server $nodes \
		-n $[$(echo "$subnets" | tr -cd '|' | wc -c) + 1] \
		-o tmp/log.offset -f tmp/log.freq \
		$([ $log_packets -ne 0 ] && echo -p tmp/log.packets) \
		-R $(awk "BEGIN {print $update_interval < 0 ? 2^-($update_interval) : 1}") \
		-r $(awk "BEGIN {print $max_sync_time * 2^$update_interval}") \
		-l $(awk "BEGIN {print $limit * 2^$update_interval}") && test_ok || test_error
}

run_ptp4l() {
	local i s subnet ifaces test_subnets conf start

	test_message 1 1 "ptp4l test with $nodes nodes:"
	print_nondefaults

	[ -z "$subnets" ] && test_subnets=$(seq -s ' ' 1 $nodes) || test_subnets=$subnets

	generate_config4 "$master_node $pmc_node $nsm_node" "$test_subnets" $time_offset \
		"$(get_wander_expr)" \
		"(+ $base_delay (* $jitter (exponential)))"

	for i in $(seq 1 $nodes); do
		ifaces="" subnet=0
		for s in $test_subnets; do
			[ $s = "|" ] && subnet=$[$subnet + 1] && continue
			[ $s -eq $i ] && ifaces="$ifaces -i eth$subnet"
		done

		test_message 2 0 "starting node $i:"
		if [ $i -eq $pmc_node ]; then
			echo "node${i}_start = $pmc_start" >> tmp/conf
			start_client $i pmc "$pmc_conf" "" "-b 0 $ifaces" && \
				test_ok || test_error
		elif [ $i -eq $nsm_node ]; then
			echo "node${i}_start = $nsm_start" >> tmp/conf
			start_client $i nsm "$nsm_conf" "" "$ifaces" && \
				test_ok || test_error
		else
			if [[ " $transparent_nodes " =~ [^0-9]$i[^0-9] ]]; then
				start=$slave_start
				conf=$transparent_conf
				step=$slave_step
			elif [ $i -eq $master_node ]; then
				start=$master_start
				conf=$master_conf
				step=$master_step
			else
				start=$slave_start
				conf=$slave_conf
				step=$slave_step
			fi
			[ -z "$step" ] || echo "node${i}_step = $step" >> tmp/conf
			echo "node${i}_start = $start" >> tmp/conf
			start_client $i ptp4l "$conf" "" "$ifaces $extra_ptp4l_options" && \
				test_ok || test_error
		fi

		[ $? -ne 0 ] && return 1
	done

	run_simulation
}

run_phc2sys() {
	local i

	test_message 1 1 "phc2sys test with $nodes nodes:"
	print_nondefaults

	for i in $(seq 1 $nodes); do
		cat >> tmp/conf <<-EOF
			node${i}_freq = $(get_wander_expr)
			node${i}_refclock = (* $jitter (normal))
			node${i}_offset = $time_offset
			node${i}_start = $slave_start
		EOF

		test_message 2 0 "starting node $i:"
		start_client $i phc2sys "$slave_conf" && test_ok || test_error
		[ $? -ne 0 ] && return 1
	done
	
	run_simulation
}