#!/bin/bash # BASH Interprocess communication demo by using simple "rendez-vous" file # # (C) 2011-2023 Felix Hauri - felix@f-hauri.ch # Licensed under terms of LGPL v3. www.gnu.org # v .03 (Based on bash >= 5.0) # This has to be sourced! Once sourced, you could use two commands # # back_func [-q] [args] # -q Quiet # command is one of # start [-g N] Start daemon to store N values (default: 60) # stop Stop daemon # -r | restart [-g N] Restart daemon clearing all values # -s | status Show running status of daemon # -g | get name [var] Get current value of "name" field and store # them into variable "var" # -d | dump Dump all fields in current state # -h | help Help system # # lastMinuteGraph [-l|-d DELAY] [-p] [-o FILE] # -l loop # -d N delay (implie -l) # -p PNG output (default to SVG) # -o File Output file (default '/dev/shm/lastMinute') # -W N Width of graphic (default 640) # -H N Height of graphic (default 480) # # First: `back_func` could run as a *daemon*, then could be asked through # other shell sessions, by `back_func -s`, dump, get or lastMinuteGraph # for build instant svg graph. # $ back_func -g procstat_load result; echo $result # $ back_func -d -v # $ back_func start -g 1800 # $ lastMinuteGraph -W 1200 -l # Then show /dev/shm/lastMinute.svg with any dynamic viewer like eyes-of-mate # # Note: back_func could do auto-completion (search for ``complete'' in this;) # # shellcheck source=/dev/null if ((UID>0)); then BF_RENDEZVOUS="/run/user/$UID/bash_ipc_demo.data" else BF_RENDEZVOUS="/run/bash_ipc_demo.data" fi back_func() { local -A MYGLOBAL local _quiet _exe _prct _load _vnam _nnam _cols _trail _var _toSleep _line local -i __int [[ $1 == -q ]] && _quiet=true && shift [[ -f $BF_RENDEZVOUS ]] && . "$BF_RENDEZVOUS" if [[ -v COMP_CWORD ]]; then # Doing automatic bash completion there case $COMP_CWORD in 1) while read -r _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) ;; dump|-d ) COMPREPLY=(-v) ;; esac ;; esac return fi case $1 in help | -h) echo "Usage: ${FUNCNAME[0]} [-q] [start" \ "[-g N]|stop|restart|status|get [var]|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 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 ]] ; 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 ) shift 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 local class=() for _vnam in "${!MYGLOBAL[@]}"; do # sort varnames on first 10 chars _nnam=${_vnam}__________ _nnam=${_nnam:0:10} class[64#$_nnam]+=$_vnam' ' done if [[ $2 == -v ]]; then _cols=999999999999999999 _trail='' else _cols=$((COLUMNS - 22)) _trail=... fi read -ra class <<<"${class[*]}" for _var in "${class[@]}"; do (( ${#MYGLOBAL[$_var]} > _cols )) && printf '%-16s: %s%s\n' "$_var" "${MYGLOBAL[$_var]::_cols}" \ $_trail || printf "%-16s: %s\n" "$_var" "${MYGLOBAL[$_var]}" done return ;; get | -g ) ### return only 1 field from snapshot. if [[ $3 ]]; then printf -v "$3" %s "${MYGLOBAL[$2]}" else printf %s\\n "${MYGLOBAL[$2]}" fi return ;; start_real ) shift;; * ) echo Bad syntax.;return 1 ;; ### Exit if $1 <> 'start_real' esac ### Real daemon script begin there... ### Close STDIN, STDOUT, STDERR and even other local _i for _i in "/proc/$$/fd"/*; do [[ -e $_i ]] && # eval "exec $_i>&-" eval "exec ${_i##*/}>/tmp/debug-$$-${_i##*/}" done unset MYGLOBAL declare -A MYGLOBAL MYGLOBAL[backFunc_pid]=$BASHPID MYGLOBAL[backFunc_running]=yes MYGLOBAL[backFunc_start]=$EPOCHREALTIME printf -v MYGLOBAL['backFunc_startH'] "%(%F %T)T.%s" \ "${MYGLOBAL[backFunc_start]%.*}" "${MYGLOBAL[backFunc_start]#*.}" [[ $1 == -g ]] && [[ $2 ]] && (($2>2)) && (($2<999999)) && printf -v MYGLOBAL['uptime_graph_val'] %d "$2" || printf -v MYGLOBAL['uptime_graph_val'] %d 60 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 15 exec {_var}<> <(: b) MYGLOBAL['dummyFd']=$_var IFS=' /' read -r MYGLOBAL['uptime_graph_val'] )) && { MYGLOBAL['uptime_useGraph']="${MYGLOBAL['uptime_useGraph']#* }" MYGLOBAL['loadavg_pidGraph']="${MYGLOBAL['loadavg_pidGraph']#* }" } declare -p MYGLOBAL > "$BF_RENDEZVOUS" MYGLOBAL[backFunc_now]=${EPOCHREALTIME} _toSleep=$((2000000-10#${MYGLOBAL[backFunc_now]#*.})) read -rt .${_toSleep:1} -u "${MYGLOBAL[dummyFd]}" _ done } complete -F back_func{,} lastMinuteGraph() { local y1 path path2 exe png opt delay OPTIND string r rt txt ytxt \ outFile=/dev/shm/lastMinute width=640 height=480 valtime MYGLOBAL local -a newpids cpuload local -i x x2 m t y2 while getopts "hlpd:o:W:H:" opt;do case $opt in h ) echo "Usage: ${FUNCNAME[0]} [-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[0]} Error: $opt not understood.";return 1;; esac;done [[ $delay ]] && { while ! read -rn 1 -t "$delay" _ ;do lastMinuteGraph $png -W "$width" -H "$height" -o "$outFile" done return 0 } [[ -z $png ]] && exe="cat >$outFile.svg" || \ exe="inkscape -z --export-png=$outFile.png >/dev/null 2>&1 /dev/stdin" . "$BF_RENDEZVOUS" path="M0,0 L" path2="M0,0 L" m=1 read -ra newpids <<<"${MYGLOBAL[loadavg_pidGraph]}" read -ra cpuload <<<"${MYGLOBAL[uptime_useGraph]}" for ((x=0;x<${#newpids[@]};x=x2)); do x2='x+1' y1=${cpuload[x]} y2=${newpids[x]} path+=" $x $y1 $x2 $y1" path2+=" $x $y2 $x2 $y2" (( y2 > m )) && m=y2 t=x done path+=" $x 0" path2+=" $x 0" valtime=${MYGLOBAL[backFunc_now]%.*} printf -v string '%(%F %T)T - %(%T)T (max %d newpids at %(%T)T)' \ $((valtime - x)) $((valtime)) "$m" $((valtime - x + 1 + t)) r=00000000$((100000000/m)) # pid /n pid vs cpu / 100% printf -v r "%.5f" ${r:0:${#r}-6}.${r:${#r}-6} path+=" L $x,0 z" rt=000000$((1000000*width*100/x/height)) # ratio for text printf -v rt "%.5f" ${rt:0:-6}.${rt:${#rt}-6} txt=000000$((15000000*x/width)) # txt size printf -v txt "%.5f" ${txt:0:-6}.${txt:${#txt}-6} ytxt=000000$((15000000*x/width+2000000)) # txt Y pos printf -v ytxt "%.5f" ${ytxt:0:-6}.${ytxt:${#ytxt}-6} eval "$exe" <<-endOfSvgFile $string endOfSvgFile } ### some functions bfsleeped () { local -A MYGLOBAL; . "$BF_RENDEZVOUS" local toSleep=$((2000000-10#${MYGLOBAL[backFunc_now]#*.})) echo "Last now: ${MYGLOBAL[backFunc_now]}... last sleep: .${toSleep:1}" } bftimchck () { local -A MYGLOBAL; . "$BF_RENDEZVOUS" local bfGap sysStarted=$(( ${MYGLOBAL[backFunc_start]/.} - ${MYGLOBAL[procstat_start]/.}0000 )) printf -v sysStarted "%.6f" ${sysStarted:0:-6}.${sysStarted:-6} bfGap=000000$(( ${sysStarted/.} + ${MYGLOBAL[uptime_up]/.}0000 - ${MYGLOBAL[backFunc_now]/.} )) printf "System started: %(%F %T)T.%s, now: %(%F %T)T.%s, BF Gap: %.6f\n" \ "${sysStarted%.*}" "${sysStarted#*.}" "${MYGLOBAL[backFunc_now]%.*}"\ "${MYGLOBAL[backFunc_now]#*.}" "${bfGap::-6}.${bfGap: -6}" } bfstatdt () { local -A MYGLOBAL; . "$BF_RENDEZVOUS" local -a pnt1 pnt2 read -ra pnt1 <<<"${MYGLOBAL[loadavg_pidGraph]}" read -ra pnt2 <<<"${MYGLOBAL[uptime_useGraph]}" printf "Crt: %d / %d -> ut: %d (%d), pid: %d (%d)\n" \ "${MYGLOBAL[backFunc_count]}" "${MYGLOBAL[uptime_graph_val]}" \ "${#pnt1[@]}" "${#MYGLOBAL[loadavg_pidGraph]}" \ "${#pnt2[@]}" "${#MYGLOBAL[uptime_useGraph]}" }