1*2f4d9adbSJeremy L Thompson#!/bin/bash 2*2f4d9adbSJeremy L Thompson 3*2f4d9adbSJeremy L Thompson# Copyright (c) 2017-2018, Lawrence Livermore National Security, LLC. 4*2f4d9adbSJeremy L Thompson# Produced at the Lawrence Livermore National Laboratory. LLNL-CODE-734707. 5*2f4d9adbSJeremy L Thompson# All Rights reserved. See files LICENSE and NOTICE for details. 6*2f4d9adbSJeremy L Thompson# 7*2f4d9adbSJeremy L Thompson# This file is part of CEED, a collection of benchmarks, miniapps, software 8*2f4d9adbSJeremy L Thompson# libraries and APIs for efficient high-order finite element and spectral 9*2f4d9adbSJeremy L Thompson# element discretizations for exascale applications. For more information and 10*2f4d9adbSJeremy L Thompson# source code availability see http://github.com/ceed. 11*2f4d9adbSJeremy L Thompson# 12*2f4d9adbSJeremy L Thompson# The CEED research is supported by the Exascale Computing Project 17-SC-20-SC, 13*2f4d9adbSJeremy L Thompson# a collaborative effort of two U.S. Department of Energy organizations (Office 14*2f4d9adbSJeremy L Thompson# of Science and the National Nuclear Security Administration) responsible for 15*2f4d9adbSJeremy L Thompson# the planning and preparation of a capable exascale ecosystem, including 16*2f4d9adbSJeremy L Thompson# software, applications, hardware, advanced system engineering and early 17*2f4d9adbSJeremy L Thompson# testbed platforms, in support of the nation's exascale computing imperative. 18*2f4d9adbSJeremy L Thompson 19*2f4d9adbSJeremy L Thompsonthis_file="${BASH_SOURCE[0]}" 20*2f4d9adbSJeremy L Thompsonif [[ "${#BASH_ARGV[@]}" -ne "$#" ]]; then 21*2f4d9adbSJeremy L Thompson script_is_sourced="yes" 22*2f4d9adbSJeremy L Thompson exit_cmd=return 23*2f4d9adbSJeremy L Thompsonelse 24*2f4d9adbSJeremy L Thompson script_is_sourced="" 25*2f4d9adbSJeremy L Thompson exit_cmd=exit 26*2f4d9adbSJeremy L Thompsonfi 27*2f4d9adbSJeremy L Thompsontest_file="" 28*2f4d9adbSJeremy L Thompsonbackend_list="/cpu/self" 29*2f4d9adbSJeremy L Thompsonrun="" 30*2f4d9adbSJeremy L Thompsonnum_proc_run=${num_proc_run:-""} 31*2f4d9adbSJeremy L Thompsonnum_proc_node=${num_proc_node:-""} 32*2f4d9adbSJeremy L Thompsondry_run="" # empty string = NO 33*2f4d9adbSJeremy L Thompsonstart_shell="" 34*2f4d9adbSJeremy L Thompsonverbose="" 35*2f4d9adbSJeremy L Thompsoncur_dir="$PWD" 36*2f4d9adbSJeremy L Thompson 37*2f4d9adbSJeremy L Thompsonmpiexec="mpirun" 38*2f4d9adbSJeremy L Thompsonmpiexec_np="-np" 39*2f4d9adbSJeremy L Thompsonmpiexec_opts="" 40*2f4d9adbSJeremy L Thompsonmpiexec_post_opts="" 41*2f4d9adbSJeremy L Thompsonprofiler="" 42*2f4d9adbSJeremy L Thompson 43*2f4d9adbSJeremy L Thompsonfunction abspath() 44*2f4d9adbSJeremy L Thompson{ 45*2f4d9adbSJeremy L Thompson local outvar="$1" path="$2" cur_dir="$PWD" 46*2f4d9adbSJeremy L Thompson cd "$path" && path="$PWD" && cd "$cur_dir" && eval "$outvar=\"$path\"" 47*2f4d9adbSJeremy L Thompson} 48*2f4d9adbSJeremy L Thompson 49*2f4d9adbSJeremy L Thompsonabspath root_dir ".." || $exit_cmd 1 50*2f4d9adbSJeremy L Thompsonbuild_root="$root_dir/build" 51*2f4d9adbSJeremy L Thompson 52*2f4d9adbSJeremy L Thompsonif [[ -t 1 ]]; then 53*2f4d9adbSJeremy L Thompson # ANSI color codes 54*2f4d9adbSJeremy L Thompson none=$'\E[0m' 55*2f4d9adbSJeremy L Thompson red=$'\E[0;31m' 56*2f4d9adbSJeremy L Thompson green=$'\E[0;32m' 57*2f4d9adbSJeremy L Thompson yellow=$'\E[0;33m' 58*2f4d9adbSJeremy L Thompson blue=$'\E[0;34m' 59*2f4d9adbSJeremy L Thompson bblue=$'\E[1;34m' 60*2f4d9adbSJeremy L Thompson magenta=$'\E[0;35m' 61*2f4d9adbSJeremy L Thompson cyan=$'\E[0;36m' 62*2f4d9adbSJeremy L Thompson clear="$(tput sgr0)" 63*2f4d9adbSJeremy L Thompsonfi 64*2f4d9adbSJeremy L Thompson 65*2f4d9adbSJeremy L Thompsonhelp_msg=" 66*2f4d9adbSJeremy L Thompson$this_file [options] 67*2f4d9adbSJeremy L Thompson 68*2f4d9adbSJeremy L ThompsonOptions: 69*2f4d9adbSJeremy L Thompson -h|--help print this usage information and exit 70*2f4d9adbSJeremy L Thompson -c|--ceed \"list\" choose the libCEED backends to benchmark 71*2f4d9adbSJeremy L Thompson -r|--run <name> run the tests in the script <name> 72*2f4d9adbSJeremy L Thompson -n|--num-proc \"list\" total number of MPI tasks to use in the tests 73*2f4d9adbSJeremy L Thompson -p|--proc-node \"list\" number of MPI tasks per node to use in the tests 74*2f4d9adbSJeremy L Thompson -d|--dry-run show (but do not run) the commands for the tests 75*2f4d9adbSJeremy L Thompson -s|--shell execute bash shell commands before running the test 76*2f4d9adbSJeremy L Thompson -v|--verbose print additional messages 77*2f4d9adbSJeremy L Thompson -x enable script tracing with 'set -x' 78*2f4d9adbSJeremy L Thompson var=value define shell variables; evaluated with 'eval' 79*2f4d9adbSJeremy L Thompson 80*2f4d9adbSJeremy L ThompsonThis script builds and runs a set of benchmarks for a list of specified 81*2f4d9adbSJeremy L Thompsonbackends. 82*2f4d9adbSJeremy L Thompson 83*2f4d9adbSJeremy L ThompsonExample usage: 84*2f4d9adbSJeremy L Thompson $this_file --run petsc-bp1.sh 85*2f4d9adbSJeremy L Thompson" 86*2f4d9adbSJeremy L Thompson 87*2f4d9adbSJeremy L Thompson 88*2f4d9adbSJeremy L Thompsonfunction build_examples() 89*2f4d9adbSJeremy L Thompson{ 90*2f4d9adbSJeremy L Thompson for example; do 91*2f4d9adbSJeremy L Thompson # We require the examples to be already built because we do not know what 92*2f4d9adbSJeremy L Thompson # options to use when building the library + examples. 93*2f4d9adbSJeremy L Thompson if [ ! -e $build_root/$example ]; then 94*2f4d9adbSJeremy L Thompson echo "Error: example is not built: $example" 95*2f4d9adbSJeremy L Thompson return 1 96*2f4d9adbSJeremy L Thompson fi 97*2f4d9adbSJeremy L Thompson done 98*2f4d9adbSJeremy L Thompson} 99*2f4d9adbSJeremy L Thompson 100*2f4d9adbSJeremy L Thompson 101*2f4d9adbSJeremy L Thompsonfunction compose_mpi_run_command() 102*2f4d9adbSJeremy L Thompson{ 103*2f4d9adbSJeremy L Thompson mpi_run="${mpiexec:-mpirun} ${mpiexec_opts}" 104*2f4d9adbSJeremy L Thompson mpi_run+=" ${mpiexec_np:--np} ${num_proc_run} ${mpiexec_post_opts}" 105*2f4d9adbSJeremy L Thompson if [[ -n "$profiler" ]]; then 106*2f4d9adbSJeremy L Thompson mpi_run+=" $profiler" 107*2f4d9adbSJeremy L Thompson fi 108*2f4d9adbSJeremy L Thompson} 109*2f4d9adbSJeremy L Thompson 110*2f4d9adbSJeremy L Thompson 111*2f4d9adbSJeremy L Thompsonfunction quoted_echo() 112*2f4d9adbSJeremy L Thompson{ 113*2f4d9adbSJeremy L Thompson local arg= string= 114*2f4d9adbSJeremy L Thompson for arg; do 115*2f4d9adbSJeremy L Thompson if [[ -z "${arg##* *}" ]]; then 116*2f4d9adbSJeremy L Thompson string+=" \"${arg//\"/\\\"}\"" 117*2f4d9adbSJeremy L Thompson else 118*2f4d9adbSJeremy L Thompson string+=" $arg" 119*2f4d9adbSJeremy L Thompson fi 120*2f4d9adbSJeremy L Thompson done 121*2f4d9adbSJeremy L Thompson printf "%s\n" "${string# }" 122*2f4d9adbSJeremy L Thompson} 123*2f4d9adbSJeremy L Thompson 124*2f4d9adbSJeremy L Thompson 125*2f4d9adbSJeremy L Thompsonfunction set_num_nodes() 126*2f4d9adbSJeremy L Thompson{ 127*2f4d9adbSJeremy L Thompson if [[ -n "$num_proc_node" ]]; then 128*2f4d9adbSJeremy L Thompson ((num_proc_run % num_proc_node != 0)) && { 129*2f4d9adbSJeremy L Thompson echo "The total number of tasks ($num_proc_run) must be a multiple of" 130*2f4d9adbSJeremy L Thompson echo "the number of tasks per node ($num_proc_node). Stop." 131*2f4d9adbSJeremy L Thompson return 1 132*2f4d9adbSJeremy L Thompson } 133*2f4d9adbSJeremy L Thompson ((num_nodes = num_proc_run / num_proc_node)) 134*2f4d9adbSJeremy L Thompson else 135*2f4d9adbSJeremy L Thompson num_proc_node="unknown number of" 136*2f4d9adbSJeremy L Thompson num_nodes="" 137*2f4d9adbSJeremy L Thompson fi 138*2f4d9adbSJeremy L Thompson echo "Running the tests using a total of $num_proc_run MPI tasks ..." | tee -a $output_file 139*2f4d9adbSJeremy L Thompson echo "... with $num_proc_node tasks per node ..." | tee -a $output_file 140*2f4d9adbSJeremy L Thompson echo | tee -a $output_file 141*2f4d9adbSJeremy L Thompson} 142*2f4d9adbSJeremy L Thompson 143*2f4d9adbSJeremy L Thompson 144*2f4d9adbSJeremy L Thompson### Process command line parameters 145*2f4d9adbSJeremy L Thompson 146*2f4d9adbSJeremy L Thompsonwhile [ $# -gt 0 ]; do 147*2f4d9adbSJeremy L Thompson 148*2f4d9adbSJeremy L Thompsoncase "$1" in 149*2f4d9adbSJeremy L Thompson -h|--help) 150*2f4d9adbSJeremy L Thompson # Echo usage information 151*2f4d9adbSJeremy L Thompson echo "$help_msg" 152*2f4d9adbSJeremy L Thompson $exit_cmd 153*2f4d9adbSJeremy L Thompson ;; 154*2f4d9adbSJeremy L Thompson -c|--ceed) 155*2f4d9adbSJeremy L Thompson shift 156*2f4d9adbSJeremy L Thompson [ $# -gt 0 ] || { 157*2f4d9adbSJeremy L Thompson echo "Missing \"list\" in --ceed \"list\""; $exit_cmd 1; } 158*2f4d9adbSJeremy L Thompson backend_list="$1" 159*2f4d9adbSJeremy L Thompson ;; 160*2f4d9adbSJeremy L Thompson -r|--run) 161*2f4d9adbSJeremy L Thompson run=on 162*2f4d9adbSJeremy L Thompson shift 163*2f4d9adbSJeremy L Thompson [ $# -gt 0 ] || { echo "Missing <name> in --run <name>"; $exit_cmd 1; } 164*2f4d9adbSJeremy L Thompson test_file="$1" 165*2f4d9adbSJeremy L Thompson [[ -r "$test_file" ]] || { 166*2f4d9adbSJeremy L Thompson echo "Test script not found: '$1'"; $exit_cmd 1 167*2f4d9adbSJeremy L Thompson } 168*2f4d9adbSJeremy L Thompson ;; 169*2f4d9adbSJeremy L Thompson -n|--num-proc) 170*2f4d9adbSJeremy L Thompson shift 171*2f4d9adbSJeremy L Thompson [ $# -gt 0 ] || { 172*2f4d9adbSJeremy L Thompson echo "Missing \"list\" in --num-proc \"list\""; $exit_cmd 1; } 173*2f4d9adbSJeremy L Thompson num_proc_run="$1" 174*2f4d9adbSJeremy L Thompson ;; 175*2f4d9adbSJeremy L Thompson -p|--proc-node) 176*2f4d9adbSJeremy L Thompson shift 177*2f4d9adbSJeremy L Thompson [ $# -gt 0 ] || { 178*2f4d9adbSJeremy L Thompson echo "Missing \"list\" in --proc-node \"list\""; $exit_cmd 1; } 179*2f4d9adbSJeremy L Thompson num_proc_node="$1" 180*2f4d9adbSJeremy L Thompson ;; 181*2f4d9adbSJeremy L Thompson -d|--dry-run) 182*2f4d9adbSJeremy L Thompson dry_run="quoted_echo" 183*2f4d9adbSJeremy L Thompson ;; 184*2f4d9adbSJeremy L Thompson -s|--shell) 185*2f4d9adbSJeremy L Thompson start_shell="yes" 186*2f4d9adbSJeremy L Thompson ;; 187*2f4d9adbSJeremy L Thompson -v|--verbose) 188*2f4d9adbSJeremy L Thompson verbose="yes" 189*2f4d9adbSJeremy L Thompson ;; 190*2f4d9adbSJeremy L Thompson -x) 191*2f4d9adbSJeremy L Thompson set -x 192*2f4d9adbSJeremy L Thompson ;; 193*2f4d9adbSJeremy L Thompson *=*) 194*2f4d9adbSJeremy L Thompson eval "$1" || { echo "Error evaluating argument: $1"; $exit_cmd 1; } 195*2f4d9adbSJeremy L Thompson ;; 196*2f4d9adbSJeremy L Thompson *) 197*2f4d9adbSJeremy L Thompson echo "Unknown option: '$1'" 198*2f4d9adbSJeremy L Thompson $exit_cmd 1 199*2f4d9adbSJeremy L Thompson ;; 200*2f4d9adbSJeremy L Thompsonesac 201*2f4d9adbSJeremy L Thompson 202*2f4d9adbSJeremy L Thompsonshift 203*2f4d9adbSJeremy L Thompsondone # while ... 204*2f4d9adbSJeremy L Thompson# Done processing command line parameters 205*2f4d9adbSJeremy L Thompson 206*2f4d9adbSJeremy L Thompson 207*2f4d9adbSJeremy L Thompsonnum_proc_list=(${num_proc_run:-4}) 208*2f4d9adbSJeremy L Thompsonnum_proc_list_size=${#num_proc_list[@]} 209*2f4d9adbSJeremy L Thompsonnum_proc_node_list=(${num_proc_node:-4}) 210*2f4d9adbSJeremy L Thompsonnum_proc_node_list_size=${#num_proc_node_list[@]} 211*2f4d9adbSJeremy L Thompson(( num_proc_list_size != num_proc_node_list_size )) && { 212*2f4d9adbSJeremy L Thompson echo " 213*2f4d9adbSJeremy L ThompsonThe size of the number-of-processors list (option --num-proc) must be the same 214*2f4d9adbSJeremy L Thompsonas the size of the number-of-processors-per-node list (option --proc-node)." 215*2f4d9adbSJeremy L Thompson echo 216*2f4d9adbSJeremy L Thompson $exit_cmd 1 217*2f4d9adbSJeremy L Thompson} 218*2f4d9adbSJeremy L Thompson 219*2f4d9adbSJeremy L Thompson 220*2f4d9adbSJeremy L Thompson### Loop over backends 221*2f4d9adbSJeremy L Thompson 222*2f4d9adbSJeremy L Thompsonfor backend in $backend_list; do 223*2f4d9adbSJeremy L Thompson( ## Run each backend in its own environment 224*2f4d9adbSJeremy L Thompson 225*2f4d9adbSJeremy L Thompson### Setup output 226*2f4d9adbSJeremy L Thompson### Test name 227*2f4d9adbSJeremy L Thompsoncd "$cur_dir" 228*2f4d9adbSJeremy L Thompsonabspath test_dir "$(dirname "$test_file")" || $exit_cmd 1 229*2f4d9adbSJeremy L Thompsontest_basename="$(basename "$test_file")" 230*2f4d9adbSJeremy L Thompsontest_file="${test_dir}/${test_basename}" 231*2f4d9adbSJeremy L Thompson### Backend name 232*2f4d9adbSJeremy L Thompsonshort_backend=${backend//[\/]} 233*2f4d9adbSJeremy L Thompson### Output file 234*2f4d9adbSJeremy L Thompsonoutput_file="${test_file%%.*}-$short_backend-output.txt" 235*2f4d9adbSJeremy L Thompsonrm -rf output_file 236*2f4d9adbSJeremy L Thompson 237*2f4d9adbSJeremy L Thompson### Setup the environment based on $backend 238*2f4d9adbSJeremy L Thompson 239*2f4d9adbSJeremy L Thompsonecho 240*2f4d9adbSJeremy L Thompsonecho "Using backend $backend ..." | tee $output_file 241*2f4d9adbSJeremy L Thompson 242*2f4d9adbSJeremy L Thompson### Run the tests (building and running $test_file) 243*2f4d9adbSJeremy L Thompson 244*2f4d9adbSJeremy L Thompson[ -n "$run" ] && { 245*2f4d9adbSJeremy L Thompson 246*2f4d9adbSJeremy L Thompson[[ "$verbose" = "yes" ]] && { 247*2f4d9adbSJeremy L Thompson echo "Test problem file, $test_basename:" | tee -a $output_file 248*2f4d9adbSJeremy L Thompson echo "------------------------------------------------" | tee -a $output_file 249*2f4d9adbSJeremy L Thompson cat $test_file | tee -a $output_file 250*2f4d9adbSJeremy L Thompson echo "------------------------------------------------" | tee -a $output_file 251*2f4d9adbSJeremy L Thompson echo | tee -a $output_file 252*2f4d9adbSJeremy L Thompson} 253*2f4d9adbSJeremy L Thompson 254*2f4d9adbSJeremy L Thompsontest_exe_dir="$build_root" 255*2f4d9adbSJeremy L Thompson 256*2f4d9adbSJeremy L Thompsontrap 'printf "\nScript interrupted.\n"; '$exit_cmd' 33' INT 257*2f4d9adbSJeremy L Thompson 258*2f4d9adbSJeremy L Thompson## Source the test script file. 259*2f4d9adbSJeremy L Thompsonecho "Reading test file: $test_file" | tee -a $output_file 260*2f4d9adbSJeremy L Thompsonecho | tee -a $output_file 261*2f4d9adbSJeremy L Thompsontest_required_examples="" 262*2f4d9adbSJeremy L Thompson. "$test_file" || $exit_cmd 1 263*2f4d9adbSJeremy L Thompson 264*2f4d9adbSJeremy L Thompson## Build files required by the test 265*2f4d9adbSJeremy L Thompsonecho "Example(s) required by the test: $test_required_examples" | tee -a $output_file 266*2f4d9adbSJeremy L Thompsonbuild_examples $test_required_examples || $exit_cmd 1 267*2f4d9adbSJeremy L Thompsonecho | tee -a $output_file 268*2f4d9adbSJeremy L Thompson 269*2f4d9adbSJeremy L Thompson## Loop over the number-of-processors list. 270*2f4d9adbSJeremy L Thompsonfor (( num_proc_idx = 0; num_proc_idx < num_proc_list_size; num_proc_idx++ )) 271*2f4d9adbSJeremy L Thompsondo 272*2f4d9adbSJeremy L Thompson 273*2f4d9adbSJeremy L Thompsonnum_proc_run="${num_proc_list[$num_proc_idx]}" 274*2f4d9adbSJeremy L Thompsonnum_proc_node="${num_proc_node_list[$num_proc_idx]}" 275*2f4d9adbSJeremy L Thompson 276*2f4d9adbSJeremy L Thompsonset_num_nodes || $exit_cmd 1 277*2f4d9adbSJeremy L Thompsoncompose_mpi_run_command 278*2f4d9adbSJeremy L Thompson 279*2f4d9adbSJeremy L Thompsonif [[ "$start_shell" = "yes" ]]; then 280*2f4d9adbSJeremy L Thompson if [[ ! -t 1 ]]; then 281*2f4d9adbSJeremy L Thompson echo "Standard output is not a terminal. Stop." | tee -a $output_file 282*2f4d9adbSJeremy L Thompson $exit_cmd 1 283*2f4d9adbSJeremy L Thompson fi 284*2f4d9adbSJeremy L Thompson echo "Reading shell commands, type 'c' to continue, 'exit' to stop ..." | tee -a $output_file 285*2f4d9adbSJeremy L Thompson echo | tee -a $output_file 286*2f4d9adbSJeremy L Thompson cd "$cur_dir" 287*2f4d9adbSJeremy L Thompson set -o emacs 288*2f4d9adbSJeremy L Thompson PS1='$ ' 289*2f4d9adbSJeremy L Thompson [[ -r $HOME/.bashrc ]] && source $HOME/.bashrc 290*2f4d9adbSJeremy L Thompson HISTFILE="$root_dir/.bash_history" 291*2f4d9adbSJeremy L Thompson history -c 292*2f4d9adbSJeremy L Thompson history -r 293*2f4d9adbSJeremy L Thompson # bind '"\\C-i": menu-complete' 294*2f4d9adbSJeremy L Thompson alias c='break' 295*2f4d9adbSJeremy L Thompson while cwd="$PWD/" cwd="${cwd#${root_dir}/}" cwd="${cwd%/}" \ 296*2f4d9adbSJeremy L Thompson prompt="[${cyan}benchmarks$none:$blue$cwd$clear]\$ " && \ 297*2f4d9adbSJeremy L Thompson read -p "$prompt" -e line; do 298*2f4d9adbSJeremy L Thompson history -s "$line" 299*2f4d9adbSJeremy L Thompson history -w 300*2f4d9adbSJeremy L Thompson shopt -q -s expand_aliases 301*2f4d9adbSJeremy L Thompson eval "$line" 302*2f4d9adbSJeremy L Thompson shopt -q -u expand_aliases 303*2f4d9adbSJeremy L Thompson done 304*2f4d9adbSJeremy L Thompson [[ "${#line}" -eq 0 ]] && { echo; $exit_cmd 0; } 305*2f4d9adbSJeremy L Thompson shopt -q -u expand_aliases 306*2f4d9adbSJeremy L Thompson echo "Continuing ..." | tee -a $output_file 307*2f4d9adbSJeremy L Thompsonfi 308*2f4d9adbSJeremy L Thompson 309*2f4d9adbSJeremy L Thompson# Call the function run_tests defined inside the $test_file 310*2f4d9adbSJeremy L Thompsonceed=$backend 311*2f4d9adbSJeremy L Thompsonif [ -z "$dry_run" ]; then 312*2f4d9adbSJeremy L Thompson run_tests >> $output_file 313*2f4d9adbSJeremy L Thompsonelse 314*2f4d9adbSJeremy L Thompson run_tests 315*2f4d9adbSJeremy L Thompsonfi 316*2f4d9adbSJeremy L Thompsonecho 317*2f4d9adbSJeremy L Thompson 318*2f4d9adbSJeremy L Thompsondone ## End of loop over processor numbers 319*2f4d9adbSJeremy L Thompson 320*2f4d9adbSJeremy L Thompsontrap - INT 321*2f4d9adbSJeremy L Thompson 322*2f4d9adbSJeremy L Thompson} ## run is on 323*2f4d9adbSJeremy L Thompson 324*2f4d9adbSJeremy L Thompson$exit_cmd 0 325*2f4d9adbSJeremy L Thompson 326*2f4d9adbSJeremy L Thompson) || { 327*2f4d9adbSJeremy L Thompson echo "Sub-shell for backend '$backend' returned error code $?. Stop." 328*2f4d9adbSJeremy L Thompson $exit_cmd 1 329*2f4d9adbSJeremy L Thompson} 330*2f4d9adbSJeremy L Thompsondone ## Loop over $backend_list 331*2f4d9adbSJeremy L Thompson 332*2f4d9adbSJeremy L Thompson 333*2f4d9adbSJeremy L Thompson$exit_cmd 0 334*2f4d9adbSJeremy L Thompson 335