#  BASH Interprocess communication demo by using simple "rendez-vous" file
#
# (C) 2011-2019 Felix Hauri - felix@f-hauri.ch
# Licensed under terms of LGPL v3. www.gnu.org
# v .02

BF_RENDEZVOUS=/dev/shm/foo

back_func() {
    local -A MYGLOBAL
    local quiet exe
    [ "$1" = "-q" ] && quiet=true && shift
    [ -f $BF_RENDEZVOUS ] && . $BF_RENDEZVOUS 
    if [ "$COMP_CWORD" ] ;then    # Doing automatic bash completion there
	case $COMP_CWORD in 
	    1)
	        while read line; do
	            [ -z "${line#*)}" ] && line=${line%)} &&
			line=${line% | -[hgdrs]} && [ "$line" ] &&
			[ -z "${line//*([a-z])}" ] &&
			[ -z "${line##${COMP_WORDS[1]}*}" ] &&
	                COMPREPLY+=($line)
	        done < <(declare -f back_func) ;;
	    2)
	        case ${COMP_WORDS[1]} in
		    get|-g ) for line in ${!MYGLOBAL[@]} ;do
	                [ -z "${line##${COMP_WORDS[2]}*}" ] &&
			    COMPREPLY+=($line)
	        	done
			;;
		    start ) COMPREPLY=-g ;;
		esac
	        ;;
	esac
	return
    fi
    case $1 in
	help | -h) echo "Usage: $FUNCNAME [-q] [start [-g N]|stop|restart|status|get|dump|help]"
	        echo "   -q    Quiet"
	        echo "   -g N  Start daemon, setting uptime_useGraph to N values"
	    return ;;
	stop )                                       ### Stop by killing bf pid
	    if back_func -q status;then
		kill ${MYGLOBAL["backFunc_pid"]} && return
	    fi
	    $quiet echo "No process killed."
	    return 1 ;;
	start )                                         ### Start in background
	    if back_func -q status ;then
		$quiet \
		    echo "Process ${MYGLOBAL["backFunc_pid"]} already running."
		return 1
	    fi
	    shift
	    ( back_func start_real $@ & ) </dev/null >/dev/null 2>/dev/null
	    return $? ;;
	status | -s )
	    if [ "${MYGLOBAL["backFunc_running"]}" = "yes" ] &&
		[ -d /proc/${MYGLOBAL[backFunc_pid]} ] && 
		exe=$(readlink /proc/${MYGLOBAL[backFunc_pid]}/exe) &&
		[ "${exe##*/}" = "bash" ] &&
		[ -z "$(/bin/ls /proc/${MYGLOBAL[backFunc_pid]}/fd)" ] ; then
		$quiet printf "Background loop function (%s) is running.\n" \
		    ${MYGLOBAL[backFunc_pid]}
		return 0
	    fi
	    $quiet echo "Background loop function is not running."
	    return 1 ;;
	restart | -r )
	    back_func ${quiet:+-q} stop
	    back_func -q status &&
	        inotifywait -t 3 -e close -q $BF_RENDEZVOUS >/dev/null
	    back_func ${quiet:+-q} start
	    return 1 ;;
	dump | -d )                                         ### dump all variables
	    sort <(
		paste -d ' ' <(
		    printf "%-16s\n" ${!MYGLOBAL[@]}
		) <(
		    printf "%20s\n" "${MYGLOBAL[@]}"))
	    return   ;;
	get | -g )   ### return only 1 field from snapshot.
	    printf ${3+-v} $3 "%s" "${MYGLOBAL[$2]}"
	    return   ;; 
	start_real ) shift;;
	* ) return 1 ;;                          ### Exit if $1 <> 'start_real'
    esac
    
                                 ### Close STDIN, STDOUT, STDERR and even other
    local _i
    for _i in $(/bin/ls /proc/$$/fd);do
    	eval "exec $_i>&-"
      done

    unset MYGLOBAL
    declare -A MYGLOBAL
    MYGLOBAL["backFunc_pid"]=$BASHPID
    MYGLOBAL["backFunc_running"]=yes
    printf -v MYGLOBAL["backFunc_start"] "%(%F %T)T" -1

    [ "$1" = "-g" ] && [ "$2" ] && (($2>2)) && (($2<999999)) && 
	printf -v MYGLOBAL["uptime_graph_val"] %d $2 ||
	printf -v MYGLOBAL["uptime_graph_val"] %d 60
             ### Prepare to update ``running'' and add ``end'' fields when exit
    trap "
	. $BF_RENDEZVOUS;
	printf -v MYGLOBAL['backFunc_end'] '%(%F %T)T' -1;
	MYGLOBAL['backFunc_running']=no
	declare -p MYGLOBAL > $BF_RENDEZVOUS
	exit 0
	" 0 2 3 6 9 15
                                                       ### Main loop begin here
    MYGLOBAL["cpu_numcores"]=$(grep -c ^processor /proc/cpuinfo)
    while :;do
	((MYGLOBAL["backFunc_count"]++))
	local old_utm=${MYGLOBAL["uptime_up"]//.} \
	    old_idl=${MYGLOBAL["uptime_idle"]//.} prct
	read MYGLOBAL["uptime_up"] MYGLOBAL["uptime_idle"] </proc/uptime
	prct=000$(( 100000 -
		100000 * ( ${MYGLOBAL["uptime_idle"]//.} - old_idl) /
		(${MYGLOBAL["uptime_up"]//.} - old_utm ) /
		${MYGLOBAL["cpu_numcores"]} ))
	printf -v MYGLOBAL["uptime_usage1sec"] "%.2f" \
	    ${prct:0:${#prct}-3}.${prct:${#prct}-3}
	MYGLOBAL["uptime_useGraph"]+=${MYGLOBAL["uptime_usage1sec"]}\ 
	(( ${MYGLOBAL["backFunc_count"]} > ${MYGLOBAL["uptime_graph_val"]} )) && 
	    MYGLOBAL["uptime_useGraph"]=${MYGLOBAL["uptime_useGraph"]#* }
	prct=000$(( 100000 -
		100000 * ${MYGLOBAL["uptime_idle"]//.} /
		${MYGLOBAL["uptime_up"]//.} /
		${MYGLOBAL["cpu_numcores"]} ))
	printf -v MYGLOBAL["uptime_usage"] "%.2f" \
	    ${prct:0:${#prct}-3}.${prct:${#prct}-3}
	IFS=\ / read </proc/loadavg    MYGLOBAL["loadavg_1min"] \
	    MYGLOBAL["loadavg_5min"]   MYGLOBAL["loadavg_15min"] \
	    MYGLOBAL["loadavg_active"] MYGLOBAL["loadavg_process"] \
	    MYGLOBAL["loadavg_last_pid"]
	MYGLOBAL["random"]=$(od -An -tu4 < <(head -c 3 /dev/urandom))
	printf -v MYGLOBAL["backFunc_now"] "%(%F %T)T" -1
	declare -p MYGLOBAL > $BF_RENDEZVOUS
	sleep 1
    done
}
complete -F back_func{,}

lastMinuteGraph() {
    local vars x y path outFile exe foo png opt delay OPTIND
    outFile=/dev/shm/lastMinute width=640 height=480
    while getopts "hlpd:o:W:H:" opt;do case $opt in
	    h ) echo "Usage: $FUNCNAME [-l|-d DELAY] [-p] [-o FILE]";
		echo "  -l       loop"
		echo "  -d N     delay (implie -l)"
		echo "  -p       PNG output (default to SVG)"
		echo "  -o File  Output file (default '$outFile')"
		echo "  -W N     Width of graphic (default $width)"
		echo "  -H N     Height of graphic (default $height)"
		return ;;
	    l ) delay=${delay:-1} ;;
	    p ) png=-p ;;
	    d ) delay=$OPTARG ;;
	    o ) outFile=${OPTARG%.??g} ;;
	    W ) width=$OPTARG ;;
	    H ) height=$OPTARG ;;
	    * ) echo >&2 "$FUNCNAME Error: $opt not understand."; return 1 ;;
	esac;done
    [ "$delay" ] && {
	while ! read -n 1 -t $delay foo ;do
	    lastMinuteGraph $png -o $outFile
	    done
	return 0
    }
    [ -z "$png" ] && exe="cat >$outFile.svg" || \
	exe="inkscape -z --export-png=$outFile.png >/dev/null 2>&1 /dev/stdin"
    back_func get uptime_useGraph vars
    path="M0,0 L" x=0
    for y in $vars ;do
        path+=" $((x++*3)) $y $((3*x)) $y"
    done
    path+=" L $((3*x)),0 z"
    eval $exe <<-endOfSvgFile
	<svg xmlns="http://www.w3.org/2000/svg" width="$width" height="$height" 
          preserveAspectRatio="none" viewBox="0,0,$((3*x)),100">
	  <rect style="fill:#ACE" width="$((3*x))" height="100"></rect>
	  <path style="fill:#08B" transform="matrix(1,0,0,-1,0,100)" d="$path">
	    </path>
	</svg>
	endOfSvgFile
}
