I'm trying to write a bash script that uses a function to spawn several applications in the background, and then store their PIDs in an associative array with the application name as the key. Running into a problem as I'm using a for loop to loop through another array that has the paths of the applications. After all the applications are spawned, I'll echo all the values of the array, but only the PID of the last application spawned is present. This causes several issues down the line in the rest of the script.
I've found this thread about why this might be happening, but I'm having trouble implementing the fix for my situation. Could someone please point me in the right direction?
proc_name=( application1 application2 application3 )
declare -A proc_pid
spawn_application () {
if [ $# -ne 1 ] || [ ! -x $1 ]
then
echo "$FUNCNAME: usage $FUNCNAME <proc_name>"
if [ $# -ne 1]
then
echo "$FUNCNAME: invalid amount of arguments, expected 1 received $#"
elif [ ! -x $1 ]
then
echo "$FUNCNAME: $1 is not an executable file"
fi
exit 1
else
./$1 $host_ipaddr &
proc_pid[$1]=$!
echo "$0: starting $1 with PID: ${proc_pid[$1]}"
fi
}
for proc in ${proc_name[@]}
do
spawn_application $proc
done
echo ${proc_pid[@]}
Here's the full script:
#!/bin/bash
#####################
# Functions #
#####################
# init - deletes all existing _metrics.csv files, populates array of executables, initializes map of proc_name:pid
init () {
rm ./*_metrics.csv >/dev/null 2>&1
proc_name=( application1 application2 application3 )
declare -A proc_pid
}
# spawn_application - takes a proc_name, checks that it is executable, and then runs the executable in the background
spawn_application () {
if [ $# -ne 1 ] || [ ! -x "$1" ]
then
echo "$FUNCNAME: usage $FUNCNAME <proc_name>"
if [ $# -ne 1 ]
then
echo "$FUNCNAME: invalid amount of arguments, expected 1 received $#"
elif [ ! -x "$1" ]
then
echo "$FUNCNAME: $1 is not an executable file"
fi
exit 1
else
./"$1" "$host_ipaddr" &
#proc_pid[$1]=$!
#echo "$0: starting $1 with PID: ${proc_pid[$1]}"
fi
}
# collect_proc_metrics - takes a proc_name and returns the elapsed time in seconds, cpu percentage, and memory percentage, passes these values to write_proc_metrics
collect_proc_metrics () {
if [ $# -ne 1 ]
then
echo "$FUNCNAME: usage $FUNCNAME <proc_name>"
if [ $# -ne 1 ]
then
echo "$FUNCNAME: invalid amount of arguments, expected 1 received $#"
fi
exit 1
else
read -r seconds cpu memory <<<"$(ps -q ${proc_pid["$1"]} -eo etimes=,%cpu=,%mem=)"
write_proc_metrics "$1" "$seconds" "$cpu" "$memory" &
fi
}
# collect_sys_metrics - takes no arguments, obtains the rx and tx rates, disk writes, and available disk space. Uses the last set elapsed time in seconds from the collect_proc_metrics. Automatically passes these values to write_sys_metrics
collect_sys_metrics () {
read -r rx tx <<<"$(ifstat en* | sed -n 4p | tr -s ' ' | awk '{print $7, $9}')"
disk_writes=$(iostat sda -k | tail -n 2 | tr -s ' ' | cut -d' ' -f4)
available_disk_space=$(df /dev/mapper/centos-root --output=avail --block-size=1M | tail -n 1)
write_sys_metrics "$seconds" "$rx" "$tx" "$disk_writes" "$available_disk_space" &
}
# write_proc_metrics - takes the elapsed time in seconds, cpu percentage, and memory percentage and appends them to a .csv file.
write_proc_metrics () {
if [ $# -ne 4 ]
then
echo "$FUNCNAME: usage $FUNCNAME <proc_name> <seconds> <cpu> <memory>"
echo "$FUNCNAME: invalid amount of arguments, expected 4 received $#"
else
local metricsout="$1_metrics.csv"
if [ ! -f "$metricsout" ]
then
echo "SECS,CPU%,MEM%" > "$metricsout"
fi
echo "$2,$3,$4" >> "$metricsout"
fi
}
# write_sys_metrics - takes the elapsed time in seconds, RX and TX rates, disk writes, and disk usage and appends them to a .csv file.
write_sys_metrics () {
if [ $# -ne 5 ]
then
echo "$FUNCNAME: usage $FUNCNAME <seconds> <rx> <tx> <disk_writes> <available_disk_space>"
echo "$FUNCNAME: invalid amount of arguments, expected 5 received $#"
else
local metricsout="system_metrics.csv"
if [ ! -f $metricsout ]
then
echo "SECS,RX,TX,kB_wrtn/s,MB_avail" > $metricsout
fi
echo "$1,$2,$3,$4,$5" >> system_metrics.csv
fi
}
# cleanup - loops through the proc_names in the proc_name array and kills their associated PID with a -9 flag.
cleanup () {
pkill -f application >/dev/null 2>&1
pkill -f ifstat >/dev/null 2>&1
#for proc in ${proc_name[@]}
#do
# kill -9 ${proc_pid[$proc]}
#done
}
trap cleanup EXIT
################
# Main #
################
if [ $# -ne 1 ]
then
echo "$0: usage $0 <host_ipaddr>"
echo "$0: invalid amount of arguments, expected 1 received $#"
else
init
host_ipaddr=$1
for proc in "${proc_name[@]}"
do
spawn_application "$proc"
proc_pid["$proc"]=$!
echo "$0: starting $proc with PID: ${proc_pid[$proc]}"
done
echo "${proc_pid[@]}"
# Starts the ifstat command as a background process.
ifstat -d1 -n
ifstat_pid=$( ps aux | grep "ifstat -d1" | head -1 | tr -s ' ' | cut -f2 -d' ' )
echo "$0: starting ifstat with PID: $ifstat_pid"
ifstat -nr
# Duration is how long to sample metrics in seconds. 900 seconds is 15 minutes
duration=$(( SECONDS+900 ))
# Interval is how long to wait between sampling
interval=5
while [ $SECONDS -lt $duration ]
do
for proc in "${proc_name[@]}"
do
collect_proc_metrics "$proc"
done
collect_sys_metrics
sleep $interval
done
cleanup
fi
And the output:
[rje6459@localhost Desktop]$ ./apm.sh 129.21.229.64
./apm.sh: starting APM1 with PID: 48163
./apm.sh: starting APM2 with PID: 48164
./apm.sh: starting APM3 with PID: 48165
./apm.sh: starting APM4 with PID: 48166
./apm.sh: starting APM5 with PID: 48167
./apm.sh: starting APM6 with PID: 48168
48168
./apm.sh: starting ifstat with PID: 48170