#!/bin/bash current_datetime=$(date "+%Y-%m-%d_%H:%M:%S") folder_name="${current_datetime}_ptp4l_measurement" # Get the directory of the script script_dir=$(dirname "$(realpath "$0")") # echo "$script_dir" # Create folder for this measurement in script folder folder_path="${script_dir}/${folder_name}" mkdir -p "${folder_path}" # Full log to file --- # set -x logpath="./${folder_name}/meas_log" mkdir -p "${logpath}" logfile="./${logpath}/${current_datetime}_meas.log" exec > "$logfile" 2>&1 # -------------------- # Engineer: Tim Brockmann # Purpose: wi-ptp measurement results in a 5x5 mesh grid. # The testbed is devided into distance layers for each node # e.g. for node 0: # # (n0) (l1) (l2) (l3) (l4) # (l1) (l1) (l2) (l3) (l4) # (l2) (l2) (l2) (l3) (l4) # (l3) (l3) (l3) (l3) (l4) # (l4) (l4) (l4) (l4) (l4) # # the script starts the ptp4l instance on node 0 first # and after that it starts it on the other nodes of the first layer # the measurement process follows a binary pattern to cover all # possible layer combinations # shellcheck source=/home/apu/testbed_files/apu-tb-opt/scripts/mesh/node_addresses.sh source /home/apu/testbed_files/apu-tb-opt/scripts/mesh/node_addresses.sh # Maybe from previous runs rm /tmp/tmp_* time_calc=1 layer_test=0 ptp4l_measurement=1 ssh_user="root" start_node=0 stop_node=24 NODE_LIST=$(seq $start_node 1 $stop_node) # Node layer definition layer_mask=(0 0 0 0) # LAYER_LIST=$(seq 1 1 1) LAYER_LIST=$(seq 1 1 4) layer_count=0 layer_combinations=0 time_for_each_measurement_in_sec=100 # echo "$folder_name" # Stuff for log file generation declare -A output_tmp output_tmp=() for i in {0..24}; do output_tmp[$i]=$(mktemp /tmp/tmp_"$i".XXXXXX) done # Cleans up when exiting or ctrl-c (SIGINT and INT) function cleanup { # remove tmp files from /tmp for key in "${!output_tmp[@]}"; do rm -rf "${output_tmp[$key]}" done # kill ptp4l instances on all nodes for node in {0..24}; do ssh_ptp4l_kill "$node" done set +x } trap cleanup EXIT trap cleanup INT trap cleanup SIGINT # Gets remaining time in seconds and calculates hours/minutes/seconds print_remaining_time() { total_time=$1 total_hours=$(( total_time / 3600 )) total_minutes=$(( $(( total_time / 60 ))-$(( total_hours * 60 )))) total_seconds=$(( total_time % 60 )) echo "Remaining time: $total_hours h $total_minutes m $total_seconds s" >&2 } # Parsing the tmp file data to a log file parse_tmp_data() { local base_node=$1 local current_layer_combi=$2 local layer_combinations=$3 # local csv_file=$4 local file_path="${folder_path}/node_${base_node}_as_master.csv" # If file doesnt exist if [[ ! -f "$file_path" ]]; then # Create file in path touch "$file_path" fi # Parse clock update lines for key in "${!output_tmp[@]}"; do if [[ $key -ne $base_node ]]; then if [[ -s "${output_tmp[$key]}" ]]; then echo "Temp. file ${output_tmp[$key]} is NOT empty" >&2 # check wether the file contains "clock update" line if grep -qa "clock update" "${output_tmp[$key]}"; then # flock: file lock flock "${output_tmp[$key]}" grep -a "clock update" "${output_tmp[$key]}" | awk '{$1=$1; print}' | while read -r line; do # Normalize whitespace and extract values OFFSET=$(echo "$line" | awk '{for (i=1; i<=NF; i++) if ($i == "offset") print $(i+1)}') STATE=$(echo "$line" | awk '{for (i=1; i<=NF; i++) if ($i ~ /^s[0-9]$/) print $i}') FREQ=$(echo "$line" | awk '{for (i=1; i<=NF; i++) if ($i == "freq") print $(i+1)}') PATH_DELAY=$(echo "$line" | awk '{for (i=1; i<=NF; i++) if ($i == "delay") print $(i+1)}') # Write to CSV file echo "$base_node;$key;$current_layer_combi;$layer_combinations;$OFFSET;$STATE;$FREQ;$PATH_DELAY" >> "$file_path" done else if [[ ! -d "${folder_path}/node_logs" ]]; then mkdir -p "${folder_path}/node_logs" fi if [[ ! -d "${folder_path}/base_node_logs" ]]; then mkdir -p "${folder_path}/base_node_logs" fi echo "Temp. file ${output_tmp[$key]} does not contain -clock update-" >&2 cat "${output_tmp[$key]}" > "${folder_path}/node_logs/base${base_node}_node${key}_combi${current_layer_combi}of${layer_combinations}" cat "${output_tmp[$base_node]}" > "${folder_path}/base_node_logs/base${base_node}_combi${current_layer_combi}of${layer_combinations}" fi else echo "Temp. file ${output_tmp[$key]} is empty" >&2 fi fi done sleep 5 # wait until all tmp files are read out before removing its content # DEBUG: # read -r -p "Press enter to continue" for key in "${!output_tmp[@]}"; do # clear tmp files for next round truncate -s 0 "${output_tmp[$key]}" done sleep 2 # wait until all tmp files are empty } # Converts an integer between 0 and 15 into the 4 single bits # of the binary representation (apt install bc -> for binary conversion) get_layer_mask() { local layer_combi layer_combi=$(printf "%04d\n" "$(bc <<< "obase=2; $1")") local l1=${layer_combi:"3":1} local l2=${layer_combi:"2":1} local l3=${layer_combi:"1":1} local l4=${layer_combi:"0":1} echo "$l4 $l3 $l2 $l1" } # Is the current node part of the given layer mask? is_part_of_layer_combi() { local base_node=$1 local opposite_node=$2 local -n mask_ref=$3 local distance local active distance=$(node_distance "$base_node" "$opposite_node") # local layer for key in "${!mask_ref[@]}"; do active=$((key +1)) layer=${mask_ref[key]} if [[ $layer -eq 1 ]] && [[ $distance -eq $active ]]; then echo "1" return fi done echo "0" } # Converts a node number to its grid coordinates (5 by 5 apu mesh grid) get_coordinates() { local node=$1 echo "$((node / 5)) $((node % 5))" } # Converts a host number (e.g. 0 for apu00) to the corresponding IP address get_ip() { local host_number=$1 if [[ $host_number -lt 10 ]];then clt_current=apu0$host_number else clt_current=apu$host_number fi local host_ip="${eth0IP[$clt_current]}" # echo "$host_ip" >&2 echo "$host_ip" } # Calculates the Chebyshev distance between two nodes # (diagonal included -> without is manhatten distance) chebyshev_distance() { local x1=$1 local y1=$2 local x2=$3 local y2=$4 # Calculate absolute values of differences local dx=$((x1 - x2)) local dy=$((y1 - y2)) dx=${dx#-} # Remove negative sign to get absolute value dy=${dy#-} # Remove negative sign to get absolute value # Return the maximum of the two distances echo $((dx > dy ? dx : dy)) } # Takes two nodes and calculates the chebyshev distance between them node_distance() { local base_node=$1 local base_node_x local base_node_y local opposite_node=$2 local opposite_node_x local opposite_node_y read -r base_node_x base_node_y <<< $(get_coordinates $base_node) read -r opposite_node_x opposite_node_y <<< $(get_coordinates $opposite_node) echo "$(chebyshev_distance base_node_x base_node_y opposite_node_x opposite_node_y)" } # Calculates the total layer count for one node get_layer_count() { local base_node=$1 local layer_count=0 local dist=0 for layer_node in {0..24}; do if [[ $base_node -ne $layer_node ]]; then # Calculate current distance (corresponds to layer) dist=$(node_distance "$base_node" "$layer_node") layer_count=$(( dist > layer_count ? dist : layer_count )) fi done echo "$layer_count" } # Starts the ptp4l instance on one node ssh_ptp4l_start() { # echo "start ptp4l" >&2 local host host=$(get_ip "$1") if [[ $2 -eq 1 ]]; then ssh -o StrictHostKeyChecking=no "$ssh_user"@"$host" "stdbuf -oL -eL /home/apu/wifi-ptp/ptp/ptp4l -i mesh0 -p /dev/ptp3 -m" > "${output_tmp[$1]}" 2>&1 & # wait else # slave only mode (-s) ssh -o StrictHostKeyChecking=no "$ssh_user"@"$host" "stdbuf -oL -eL /home/apu/wifi-ptp/ptp/ptp4l -i mesh0 -p /dev/ptp3 -m -s" > "${output_tmp[$1]}" 2>&1 & # wait fi } # Kills all ptp4l instances on one node ssh_ptp4l_kill() { # echo "kill ptp4l" >&2 local host host=$(get_ip "$1") ssh -o StrictHostKeyChecking=no "$ssh_user"@"$host" "pkill -9 ptp4l" & } # Checks whether the ptp4l instance is running ssh_ptp4l_check() { local host host=$(get_ip "$1") if ssh "${ssh_user}@${host}" "pgrep 'ptp4l' > /dev/null"; then # echo "PTP4l is running on apu$1 with IP ${host}." echo "1" return else # echo "PTP4l is NOT running on ${host}." echo "0" fi } # kill ptp4l instances on all nodes (just to be sure) for node in {0..24}; do ssh_ptp4l_kill "$node" done sleep 5 # wait for the nodes to stop all previous ptp4l instances # Kills all ptp4l instances except on the given base node ssh_ptp4l_kill_all_except_base_node() { local base_node=$1 for node in {0..24}; do if [[ $node -ne $base_node ]]; then ssh_ptp4l_kill "$node" fi done } # Kills all ptp4l instances ssh_ptp4l_kill_all() { for node in {0..24}; do ssh_ptp4l_kill "$node" done } count_total_measurements=0 # -------------------------------------------------------------------------------------------------- # Time Calc # -------------------------------------------------------------------------------------------------- if [[ $time_calc -eq 1 ]]; then # calculate total measurement time # all nodes for base_node in $NODE_LIST; do layer_count=0 # layers around node (Chebyshev distances -> hop count) for layer in $LAYER_LIST; do count=0 read -r x1 y1 <<< "$(get_coordinates "$base_node")" # all other nodes for layer_node in {0..24}; do # if not the current node itself if [[ $base_node -ne $layer_node ]]; then read -r x2 y2 <<< "$(get_coordinates "$layer_node")" distance=$(chebyshev_distance "$x1" "$y1" "$x2" "$y2") # If distance matches the given distance/hop count, print the pair if [[ $distance -eq $layer ]]; then ((count+=1)) fi fi done # if no node on a layer, leave it out if [[ $count -ne 0 ]]; then ((layer_count+=1)) fi done ((count_total_measurements+=$(( 2 ** layer_count -1 )))) done # The whole calculation stuff until here is only for displaying the total time total_time=$(( count_total_measurements * time_for_each_measurement_in_sec )) remaining_time=$total_time print_remaining_time "$total_time" # echo "Total Time: $total_hours h $total_minutes m $total_seconds s" fi # -------------------------------------------------------------------------------------------------- # Create csv file # -------------------------------------------------------------------------------------------------- if [[ $layer_test -eq 1 ]]; then csv_layer_test="logs/layer_test.csv" # Remove existing CSV file rm -f "$csv_layer_test" # Write first line echo "PTP4L mesh network layer test" > $csv_layer_test fi if [[ $ptp4l_measurement -eq 1 ]]; then csv_ptp4l_measurement="ptp4l_measurement.csv" # Remove existing CSV file rm -f "$csv_ptp4l_measurement" # Write first line echo "PTP4L 5by5 mesh testbed (apu) measurement results" > $csv_ptp4l_measurement echo "base_node#;node#;current_layer_combi;total_combis;offset;state;freq;path_delay" >> $csv_ptp4l_measurement fi # -------------------------------------------------------------------------------------------------- # main # -------------------------------------------------------------------------------------------------- # Iterate through node list and set current node as "base node" for base_node in $NODE_LIST; do # Get the layer count for the current base node layer_count=$(get_layer_count "$base_node") # How many layer cominations are there layer_combinations=$(( 2 ** layer_count -1 )) # DEBUG: # layer_combinations=1 # TODO: check whether the ptp4l instance is running or not # the master instance seems to be the problem when a base node # doesnt hold results # try2start=0 # while [ "$(ssh_ptp4l_check "$base_node")" -eq "0" ]; do # ((try2start+=1)) # echo "Attempt $try2start to start ptp4l on master node $base_node" # ssh_ptp4l_start "$base_node" "1" # sleep 5 # done # echo "Master is running" # Iterate through all possible layer combinations for current_layer_combi in $(seq 1 $layer_combinations); do echo "Base node: $base_node Layer combination: $current_layer_combi of $layer_combinations" ssh_ptp4l_start "$base_node" "1" # ------------------------------------------------------------------------------------------- if [[ $layer_test -eq 1 ]]; then echo " " >> "$csv_layer_test" echo "next combo --------------------------------" >> "$csv_layer_test" fi # ------------------------------------------------------------------------------------------- # Set current layer combination mask read -r "layer_mask[3]" "layer_mask[2]" "layer_mask[1]" "layer_mask[0]" <<< "$(get_layer_mask "$current_layer_combi")" # Iterate through all nodes for opposite_node in {0..24}; do # Skip own node number if [[ $base_node -ne $opposite_node ]]; then # If current opposite_node is part of the current active layer combination is_partof=$(is_part_of_layer_combi "$base_node" "$opposite_node" layer_mask) # echo "partof: $is_partof" if [[ $is_partof -eq 1 ]]; then # ------------------------------------------------------------------------------ if [[ $layer_test -eq 1 ]]; then echo "BaseNode: $base_node OppoNode: $opposite_node Mask: ${layer_mask[@]}" >> "$csv_layer_test" fi # ------------------------------------------------------------------------------ # Start ptp4l on opposite/layer nodes ("0" for slave only mode) ssh_ptp4l_start "$opposite_node" "0" fi fi done # Wait for measurement results sleep $time_for_each_measurement_in_sec # Kill ptp4l instances on all nodes except the base node (master) # ssh_ptp4l_kill_all_except_base_node "$base_node" ssh_ptp4l_kill_all sleep 5 # wait for killing ptp4l instances on all nodes # Parse data from tmp files and write to measurement csv parse_tmp_data "$base_node" "$current_layer_combi" "$layer_combinations" # Print remaining time to console ((remaining_time-=time_for_each_measurement_in_sec)) print_remaining_time "$remaining_time" done # Kill ptp4l on base node ssh_ptp4l_kill "$base_node" done