# Copyright (C) 2013 Miroslav Lichvar # # 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 . 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 }