------------------------------------------------------------------------------- Using Bash Co-Processing From Technetra http://www.technetra.com/2009/04/26/discovering-web-access-latencies-using-bash-coprocessing/ This is an example of using co-processing in BASH using FIFO named pipes, which are linked to an array of file descriptors. However it suffers from assumptions about the file descriptor numbers being used, and does not have good cleanup coding. It is also devoid of comments on exactly what it is doing in many places. Basically it is a start of an all bash version of the "empty" coprocessing methodology, but needs more work. ------------------------------------------------------------------------------- Discovering Web Access Latencies Using Bash Co-Processing Robert Adkins, April 26th, 2009 at 10:03 am This tutorial describes a Bash co-processing script latency.sh (Listing 1) that generates and sorts web access latencies for a user supplied list of hosts. It demonstrates a co-processing scheme that supports multiple, simultaneous co-processes and works in all versions of Bash. It is an updated and parallelized version of an earlier script with a similar purpose, ffmirror.sh (see http://grulos.blogspot.com). This new script uses customized Bash co-processing support for parallelization. In addition, unlike the grulos script, this script does not require a Bash executable that has been compiled for special network redirection (that is, with compiled-in support for /dev/tcp, etc.). You can use either tcp redirection or wget by specifying the desired mode as an argument on the command line. This is good news for Debian and Ubuntu users because these distributions normally do not ship with net redirections enabled. Note that while the Bash net redirection mode is a bit faster than launching and using wget, the latter may be more robust across many iterations of running the script against large lists of both existing and non-existent hosts. Co-processing can reduce the performance profile of the principal work path of the script from linear, or O(n), to nearly O(1). The principal work path in our case is setting up host connections. In our implementation, collecting the latency results is still linear, but with a negligible run-time coefficient. latency.sh can be run with the default wget connection strategy: $ ./latency.sh < myhostlist or with the Bash network redirection strategy where available: $ ./latency.sh tcp < myhostlist Using the 20-entry sample host list at grulos.blogspot.com, the time to run this script on a normal desktop can be under 2 seconds, while the original code requires over 18 seconds. This makes it feasible to run such a command under programs like /usr/bin/watch to create dynamically updating displays. Figure 1: Trial run (using the original sample host data from grulos.blogspot.com) # time ./latency.sh tcp < new_hostlist.txt 04/26/2009 19:40:49 running tcp version 270ms www.google.com 540ms www.redhat.com 900ms www.kernel.org 940ms www.microsoft.com 940ms www.slackware.org 940ms www.w3.org 960ms www.ibm.com 990ms www.debian.org 1000ms www.wikipedia.org 1000ms www.gentoo.org 1050ms www.altavista.com 1070ms www.yahoo.com 1070ms www.dell.com 1090ms www.netbsd.org 1100ms www.freebsd.org 1220ms www.openbsd.org 1860ms www.linux.org (not set) www.shakakalasfdksasdfasdf.com (not set) www.oooooooos.com (not set) www.tttttttttt.org real 0m1.982s user 0m0.156s sys 0m0.160s Note that hosts whose latencies cannot be computed, for whatever reason, are labelled as ‘(not set)’. The Bash co-processing function presented in Listing 2 below for coprocess.bash is based on a similar example from the GNU Bash distribution. The difference is that this co-processing function implements a simple queuing facility to handle multiple simultaneous coprocesses. This is essential for us to be able to parallelize our web access application. It also uses parameterized named pipes (fifos) for communicating between coprocesses. The script latency.sh works under both Bash 3.x and Bash 4.0. It should be noted that the recent native support for co-processes in Bash 4.0 is limited to a single co-process at a time and so could not meet our need for multiple simultaneous web connections. Listing 1: latency.sh =======8<--------CUT HERE----------axes/crowbars permitted--------------- #!/bin/bash # # latency.sh - Generate and sort web access latencies # for a list of hosts # # Copyright (c) 2009 Technetra Corp # # 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 3 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 . # # This is an updated and parallelized version # of ffmirror.sh (http://grulos.blogspot.com) # # To run the default wget based version: # ./latency.sh < hostlist.txt # or for the tcp (Bash net redirection) version: # ./latency.sh tcp < hostlist.txt # ########################################################################### . ./coprocess.bash WHICH_BACKGROUNDER=${1:-wget} echo "$(date '+%m/%d/%Y %H:%M:%S') running $WHICH_BACKGROUNDER version" chk_time () { # get first part of uptime - time in seconds since last reboot local t=$(/dev/null 3<>/dev/tcp/$host/80 && # send a GET request and read 1st line of response printf "GET / HTTP/1.1\r\nHost: $host\r\n\r\n">&3 && read -u 3 -t 10 -a response && exec 3>&- && chk_time b echo "$((b-a))|$host" } # spin off parallel connections (the 'map' in 'MapReduce') while read hostname; do # provide hostname to wget or /dev/tcp as command-line argument coprocess open qindex ${WHICH_BACKGROUNDER}_backgrounder "$hostname" # alternatively, send hostname to wget or /dev/tcp via pipe: # coprocess open qindex wget_backgrounder # coprocess put $qindex "$hostname" done # collect and process results (the 'reduce' in 'MapReduce') coprocess size len for ((i=0;i<$len;i++)); do # read result from pipe coprocess get $i c coprocess close $i h=${c##*|} d=${c%%|*} if [[ d -ge 0 ]]; then # bucket sort variant e=$((d*100)) until [ "${slist[e]}" == "" ]; do ((e++)) done slist[e]="${d}0ms $h" else elist[not_set++]="(not set) $h" fi done printf "%sn" "${slist[@]}" [[ ${#elist[*]} -ge 0 ]] && printf "%sn" "${elist[@]}" =======8<--------CUT HERE----------axes/crowbars permitted--------------- Listing 2: coprocess.bash This is source by the previous program and provides coprocessing routines. No Manual Provided! =======8<--------CUT HERE----------axes/crowbars permitted--------------- # coprocess.bash # # Copyright (c) 2009 Technetra Corp # # 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 3 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 . # ########################################################################## declare -i FIFO_IN FIFO_OUT FIFO_START=60 declare -a COPROCESSQ coprocess () { local CMD="$1" N="$2" shift 2 [[ "$N" == [[:digit:]]* ]] && FIFO_IN=$((N*2+FIFO_START)); FIFO_OUT=$((FIFO_IN+1)) case "$CMD" in open) local M=${#COPROCESSQ[*]} # get next co-process index FIFO_IN=$((M*2+FIFO_START)); FIFO_OUT=$((FIFO_IN+1)) local fifo="/var/tmp/coprocess.$FIFO_IN.$FIFO_OUT.$$.$RANDOM" mkfifo "$fifo.in" || return $? mkfifo "$fifo.out" || { ret=$? rm -f "$fifo.in" return $ret } # Launch the background process ( trap "rm -f $fifo.in $fifo.out" 0 "$@" <$fifo.in >$fifo.out ) & COPROCESSQ[M]=$fifo eval "$N=$M" eval "exec $FIFO_IN>$fifo.in $FIFO_OUT<$fifo.out" return 0 ;; close) eval "exec $FIFO_IN>&- $FIFO_OUT<&-" [ "$1" = "-SIGPIPE" ] && return 1 return 0 ;; get) local old_trap=$(trap -p SIGPIPE) trap "coprocess close $N -SIGPIPE" SIGPIPE # is this needed? builtin read "$@" <&$FIFO_OUT local ret=$? eval "$old_trap" return $ret ;; put) local old_trap=$(trap -p SIGPIPE) trap "coprocess close $N -SIGPIPE" SIGPIPE builtin echo "$@" >&$FIFO_IN local ret=$? eval "$old_trap" return $ret ;; size) local M=${#COPROCESSQ[*]} eval "$N=$M" ;; esac } =======8<--------CUT HERE----------axes/crowbars permitted---------------