#!/bin/bash -p
# pstool v1.3, Finn Magnusson, finn.magnusson@ericsson.com
# Type "pstool" for help.

######################################################################################
#                                                                                    #
#  Ericsson AB 2001-2019    - All Rights Reserved                                   #
#                                                                                    #
# The copyright to the computer program(s) herein is the property   of Ericsson AB,  #
# Sweden. The programs may be used and/or copied only with the written permission    #
# from Ericsson AB or in accordance with the terms and  conditions stipulated in the #
# agreement/contract under which the program(s) have been supplied.                  #
#                                                                                    #
######################################################################################

moshelldir=`dirname "$0"`
if [[ $moshelldir != /* ]] ; then moshelldir=`pwd`/$moshelldir ; fi
vobsinstallation=0
unamea=$(uname -a)
gawkext=""
gawklib=""
if [[ $unamea = [Ll][iI][nN][uU][xX]*x86_64* ]] ; then gawklib="lin64"   ; if [[ $vobsinstallation = 1 ]] ; then gawkext=".lin64" ; fi
elif [[ $unamea = [Ll][iI][nN][uU][xX]* ]]      ; then gawklib="linux"   ; if [[ $vobsinstallation = 1 ]] ; then gawkext=".linux" ; fi
elif [[ $unamea = SunOS*sparc* ]]               ; then gawklib="solaris" ; if [[ $vobsinstallation = 1 ]] ; then gawkext=".solaris" ; fi
elif [[ $unamea = SunOS* ]]                     ; then gawklib="sol86"   ; if [[ $vobsinstallation = 1 ]] ; then gawkext=".sol86" ; fi
else gawklib="cygwin" ; gawkext=".exe"
fi
gawkprog="gawk${gawkext}" 
gawk="$moshelldir/$gawkprog"

#special case where moshell has been installed on a linux 32 bit machine but should be run on linux 64 sharing the same file system
if [[ $unamea = [Ll][iI][nN][uU][xX]*x86_64* && $vobsinstallation != 1 && `file "$moshelldir/gawk"` = *32-bit* ]] ; then gawklib="linux" ; fi

filefunc="$moshelldir/commonjars/lib/${gawklib}/filefuncs"
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}$moshelldir/commonjars/lib/${gawklib}
export LANG=C
export LC_ALL=C

user=""
monitor=0
kill=0
old=0
elapsed=0
while getopts "u:km9oe:" options
do
case $options in
k) kill=15 ;;
9) kill=9 ;;
m) monitor=1 ;;
u) user=$OPTARG ;;
e) elapsed=$OPTARG ;;
o) old=1 ;;
esac
done
shift $(($OPTIND - 1))

function print_usage()
{
cat <<EOF
Usage: pstool [-m] [-o] [-u <userid>] [-e <nrdays>] [-k|-9] list|detail|<process-filter>|<process-id>

This utility shows the resources consumed by a process and its children:
 - NCh    = Number of Children processes spawned by that process
 - %CPU   = Percentage of the CPU used by the process and its children
 - %MEM   = Percentage of the RAM memory used by the process and its children
 - RSS    = Resident Set Size, the amount of RAM memory used by the process and its children, in MB
 - VSZ    = Virtual Memory Size, the amount of Virtual memory (= RAM + swap) used by the process and its children, in MB
 - ELAPSED= The time for which a process has been running, expressed in days/hours/minutes/seconds. Eg: 345-12:04:07 = 345 days, 12h, 4m, 7s
 - aXXX   = The Average usage value of a resource
 - pXXX   = The highest (Peak) usage value of a resource

Arguments:
 list: to show the resource consumption of all moshell sessions currently running on the workstation. Eg: "pstool list"
 detail: same as "list" but shows the resource consumption of all processes spawned by each moshell session. Eg: "pstool detail"
 <process-id>: to show the resource consumption of a process and its children, given by its PID. Eg: "pstool 5874"
 <process-filter>: same as above but using a regular expression, matching the command that started the process (if contain spaces, replace spaces with .*)

Options:
- The "-m" option is for continuous monitoring of the resources consumed by the process and its children. 
- The "-u" option is to specify the owner of the process. Only needed in case the person running the pstool is different than the person who owns the process.
- The "-e" option is to specify the number of elapsed days. Eg "-e 5" to only display/kill the sessions older than 5 days.
- The "-k" option is to send the "kill" command to a process and all its children. 
- The "-9" option is to send the "kill -9" command to a process and all its children (to be used only in case the standard kill did not work).
- The "-o" option is for printing with the old format (moshell 7.1m and before)

Examples: 
>> pstool moshell.*rnc11            --> show the process tree for a process started by a command matching "moshell.*rnc11"
>> pstool -m 3214                   --> monitor resource consumption for process with pid 3214
>> pstool -u root httpd             --> show process tree for a process called "httpd" and owned by the user "root"
>> pstool -k 3214		    --> kill process 3214 and all its children (standard kill).
>> pstool -9 3214		    --> force kill process 3214 and all its children (kill -9).
>> pstool -e 10 -9 .                --> force kill all sessions older than 5 days.
>> pstool list | sort -nk 7         --> show all running moshell sessions, sorted by RSS (= RAM usage)
>> pstool list | sort -nk 8         --> show all running moshell sessions, sorted by VSZ (= RAM+swap)
>> pstool detail                    --> show all running moshell sessions, sorted by number of spawned processes
EOF
}

function pstool()
{
$gawk -v pstoolpid=$1 -v monitor=$monitor -v kill=$kill -v moshelldir="$moshelldir" -v user=$user -v old=$old -v elapsed=$elapsed -l "$filefunc" '
BEGIN{ 
	#  PID  PPID     USER %CPU %MEM  RSS  VSZ COMMAND
	#29314 29311 eanzmagn  0.0  0.0  968 1136 sh -c /home/eanzmagn/moshell/mobatch -v print_lmid=0 -l -p 60 -t 60 moshell_log
	#29323 29322 eanzmagn  0.0  0.0  968 1136 sh -c /home/eanzmagn/moshell/moshell -l -v print_lmid=0 rnc11 col ; hdinfo ; 
	
	#if (user=="") user=PROCINFO["uid"]
	"uname -a" | getline os ; close("uname -a")
	os=tolower(os)
	if (os=="linux") WW=" -ww" ; else WW=""
	if (os ~ /cygwin/) 
	{
		#filefuncs="c:/cygwin"moshelldir"/commonjars/lib/file.dll"
		print "pstool is currently not supported on CYGWIN !"
		exit
	}
	else if (os ~ /linux/) filefuncs=moshelldir"/commonjars/lib/file.so.linux"
	else if (os ~ /sunos.*sparc/) filefuncs=moshelldir"/commonjars/lib/file.so.solaris"
	else filefuncs=moshelldir"/commonjars/lib/file.so.sol86"
	#extension(filefuncs, "dlload")
	checkmem()
	if (monitor==1) processusage(pstoolpid)
	else processtree(pstoolpid)
}
function checkmem()
{
	if (os ~ /linux/) pscom="free -m 2>&1"
	else pscom="prtconf 2>&1"
	while (pscom |& getline)
	{
		#Linux
		#Mem:         32195      28914       3280          0        423      24496
		#Swap:         2055          0       2055
		#Solaris
		#Memory size: 4096 Megabytes
		if ($1=="Mem:") totRam=$2
		else if ($1=="Swap:") totSwap=$2
		else if ($1=="Memory" && $2=="size:") totRam=$3
	}
	close(pscom)
	if (os !~ /linux/) pscom="swap -s 2>&1"
	while (pscom |& getline)
	{
		#total: 4438912k bytes allocated + 201016k reserved = 4639928k used, 6573224k available
		if ($1=="total:") totSwap=sprintf("%.0f",($9+0)/1024+($11+0)/1024)
	}
	close(pscom)
	totVir=totRam+totSwap
}
function processusage(inputpid)
{
	printf("Process resource usage measurement for %s, started at %s.\n",pstoolpid,strftime("%Y-%m-%d %H:%M:%S",systime()))
	start_time=systime()
	while (1) 
	{
		processtree(inputpid)
		sleep(1)
	}
}
function processtree(inputpid,  pscom,i,found,z,array,mospid,word,tword,k,w,y,m,comline,tmpuser,totNch,totCpu,totMem,totRss,totVsz,start,foundpid,etime)
{
	#global variables: procchildren, titleline, procinfo, monitor, procchildren_list
	delete procchildren
	delete procinfo
	#pscom="ps -u "user" -o pid,ppid,user,pcpu,pmem,rss,vsz,etime,args"
	if (user != "") tmpuser=user
	else if (user == "" && inputpid ~ /^[0-9]+$/)
	{
		pscom="ls -ld /proc/"inputpid" 2>&1"
		while (pscom |& getline)
		{
			gsub("\r","")
			#/proc/123: No such file or directory
			#dr-x--x--x   5 eanzmagn  rnd          736 Mar 19  2009 /proc/1019/
			if ($1 ~ /^dr/) { tmpuser=$3 ; foundpid=1 }
		}
		close(pscom)
		if (foundpid != 1) { print_end() ; exit }
	}

	if (tmpuser!="") pscom="ps"WW" -u "tmpuser" -o pid,ppid,user,pcpu,pmem,rss,vsz,etime,args"
	else if (inputpid !~ /^[0-9]+$/) pscom="ps"WW" -e        -o pid,ppid,user,pcpu,pmem,rss,vsz,etime,args"
	#print pscom
	while (pscom |& getline)
	{
		gsub("\r","")
		if (tolower($1)=="pid") 
		{
			titleline=$0
			split(titleline,tword," ")
		}
		procchildren[$2]=procchildren[$2]","$1
		procinfo[$1]=$0
		#  PID  PPID     USER %CPU %MEM  RSS  VSZ     ELAPSED COMMAND
		# 3762  3761  eanzmagn 0.0  0.1 1192 2640 385-03:06:02 /bin/bash -p /vobs/iov/rnc/bin/moshell/moshell -v use_complete_mom=1 137.58.215
		# 1019  1018  eanzmagn 0.0  0.1 1192 2656     03:45:42 /bin/bash -p /vobs/iov/rnc/bin/moshell/moshell -v use_complete_mom=1 137.58.215
		#19140 19139  eanzmagn 0.0  0.1 2000 2640        23:57 /bin/bash -p /vobs/iov/rnc/bin/moshell/moshell -v use_complete_mom=1 137.58.215
		if ($9 ~ /\/bin\/bash$/ && $10 =="-p" && $11 ~ /moshell$/) moslist[$1]=sprintf("%030s,%010s",toseconds($8),$1)
	}
	close(pscom)
	for (i in procchildren) gsub(/^,/,"",procchildren[i])
	if (inputpid ~ /^[0-9]+$/) pstree(inputpid)
	else if (inputpid ~ /^(list|detail)$/)
	{
		monitor=2
		printf("%5s  %5s  %-8s  %3s  %4s  %4s  %6s  %6s  %12s  %s\n",tword[1],tword[2],tword[3],"nCh",tword[4],tword[5],tword[6],tword[7],tword[8],tword[9])
		print "----------------------------------------------------------------------------------------"
		z=asort(moslist)
		for (i=1;i<=z;i++)
		{
			split(moslist[i],array,",")
			mospid=array[2]+0
			etime=array[1]+0
			if ((elapsed+0)>0 && etime<(elapsed*86400)) continue
			w=split(procinfo[mospid],word," ")
			comline=""
			for (k=11;k<=w;k++) comline=comline word[k]" "
			procchildren_list=mospid
			procsize(mospid,"reset")
			find_procchildren(mospid)
			totNch+=nch
			totCpu+=cpu
			totMem+=mem
			totRss+=rss
			totVsz+=vsz
			if (inputpid == "list")
			{
				printf("%5s  %5s  %-8s  %3s  %4.1f  %4.1f  %5.1fM  %5.1fM  %12s  %s\n",word[1],word[2],word[3],nch,cpu,mem,rss/1024,vsz/1024,word[8],comline)
			}
			else if (inputpid == "detail")
			{
				y=split(procchildren_list,array,",")
				for (m=1;m<=y;m++) 
				{
					w=split(procinfo[array[m]],word," ")
					comline=""
					if (m==1) start=11 ; else start=9
					for (k=start;k<=w;k++) comline=comline word[k]" "
					printf("%5s  %5s  %-8s  %3s  %4.1f  %4.1f  %5.1fM  %5.1fM  %12s  %s\n",word[1],word[2],word[3],(m==1?nch:"-"),word[4],word[5],word[6]/1024,word[7]/1024,word[8],comline)
				}
				printf("%5s  %5s  %-8s  %3s  %4.1f  %4.1f  %5.1fM  %5.1fM  %12s  %s\n","","","","",cpu,mem,rss/1024,vsz/1024,"","")
				print "----------------------------------------------------------------------------------------"
			}
		}
		print "----------------------------------------------------------------------------------------"
		printf("Moshell Sessions: %s, Spawned Processes: %s\n",z,z+totNch)
		printf("RSS: %4.0fM of %5sM%7s\n",totRss/1024,totRam,(totRam>0?sprintf(" (%.1f%)",100*totRss/1024/totRam):""))
		printf("VSZ: %4.0fM of %5sM%7s\n",totVsz/1024,totVir,(totVir>0?sprintf(" (%.1f%)",100*totVsz/1024/totVir):""))
	}
	else 
	{ 
		if (kill > 0)
		{
			for (i in moslist)
			{
				if ((elapsed+0)>0)
				{
					split(moslist[i],array,",")
					if ((array[1]+0)<(elapsed*86400)) continue
				}
				system(sprintf("%s/pstool %s %s",moshelldir,(kill=="9"?"-9":"-k"),i))
			}
		}
		else
		{
			for (i in procinfo) 
			{ 
				if (procinfo[i] ~ inputpid) { if (pstree(i)==1) found=1}
			} 
			if (found != 1) { print_end() ; exit }
		}
	}
}
function print_end()
{
	if (monitor == 0) print "Process not found!" 
	else if (monitor == 1)
	{
		printf("Process resource usage measurement for %s, stopped at %s.\n",pstoolpid,strftime("%Y-%m-%d %H:%M:%S",systime()))
		end_time=systime()
		printf("Total measurement time: %s\n",convertseconds(end_time-start_time))
	}
}
function convertseconds(string,  nrOfHours,nrOfMinutes,nrOfSeconds,result)
{
	if (string < 60) return string"s"
	result=string"s ("
	if (string >= 3600)
	{
		nrOfHours=int(string/(3600))
		string=string%3600
	}
	nrOfMinutes=int(string/60)
	nrOfSeconds=(string%(3600))%60
	result=result (nrOfHours>0?nrOfHours"h":"") nrOfMinutes"m" nrOfSeconds"s)"
	return result
}	
function pstree(inputpid,    i,array,last,kill_session,w,k,word,tword,comline)
{
	if (procinfo[inputpid] ~ "pstool") return -1
	if (procinfo[inputpid] == "") { print_end() ; exit }
	if (monitor == 0)
	{
		procchildren_list=inputpid
		printf("#####################################################################################\n\
PROCESS TREE at %s for PID %s (%s)\n\
#####################################################################################\n",\
strftime("%Y-%m-%d %H:%M:%S",systime()),inputpid,procname(inputpid))	
		split(titleline,tword," ")
		printf("%5s  %5s  %-8s  %4s  %4s  %6s  %6s  %12s  %s\n",tword[1],tword[2],tword[3],tword[4],tword[5],tword[6],tword[7],tword[8],tword[9])
		w=split(procinfo[inputpid],word," ")
		comline=""
		for (k=9;k<=w;k++) comline=comline word[k]" "
		printf("%5s  %5s  %-8s  %4.1f  %4.1f  %5.1fM  %5.1fM  %12s  %s\n",word[1],word[2],word[3],word[4],word[5],word[6]/1024,word[7]/1024,word[8],comline)
		#print procinfo[inputpid]
	}
	procsize(inputpid,"reset")
	find_procchildren(inputpid)
	if (monitor == 0)
	{
		print "-------------------------------------------------------------------------------------"
		if (old) printf("NCh=%s %CPU=%.1f %MEM=%.1f RSS=%s VSZ=%s\n\n",nch,cpu,mem,rss,vsz)
		else printf("NCh=%s %CPU=%.1f %MEM=%.1f RSS=%.1fM VSZ=%.1fM\n\n",nch,cpu,mem,rss/1024,vsz/1024)
		if (kill > 0)
		{
			last=split(procchildren_list,array,",")
			#print procchildren_list,last
			for (i=last;i>=1;i--) 
			{
				kill_session=sprintf("kill %s%s 2>&1",(kill=="9"?"-9 ":""),array[i])
				print kill_session
				system(kill_session)
			}
		}
	}
	else if (monitor==1)
	{
		if (nch > peaknch) peaknch=nch
		if (cpu > peakcpu) peakcpu=cpu
		if (mem > peakmem) peakmem=mem
		if (rss > peakrss) peakrss=rss+0
		if (vsz > peakvsz) peakvsz=vsz+0
		nrMEAS++
		avgnch=int(totnch/nrMEAS)
		avgcpu=totcpu/nrMEAS
		avgmem=totmem/nrMEAS
		avgrss=int(totrss/nrMEAS)
		avgvsz=int(totvsz/nrMEAS)
		#old: printf("%s> %s (%s): NCh=%s %CPU=%.1f %MEM=%.1f RSS=%s VSZ=%s PeakNCh=%s Peak%CPU=%.1f Peak%MEM=%.1f PeakRSS=%s PeakVSZ=%s AvgNCh=%s Avg%CPU=%.1f Avg%MEM=%.1f AvgRSS=%d AvgVSZ=%d\n",strftime("%H:%M:%S",systime()),inputpid,procname(inputpid),nch,cpu,mem,rss,vsz,peaknch,peakcpu,peakmem,peakrss,peakvsz,avgnch,avgcpu,avgmem,avgrss,avgvsz)
		if (old)
		{
			if (old) printf("%s PID=%s> NCh=%s %CPU=%.1f %MEM=%.1f RSS=%s VSZ=%s PkNCh=%s Pk%CPU=%.1f Pk%MEM=%.1f PkRSS=%s PkVSZ=%s AvgNCh=%s Avg%CPU=%.1f Avg%MEM=%.1f AvgRSS=%d AvgVSZ=%d (%s)\n",strftime("%H:%M:%S",systime()),inputpid,nch,cpu,mem,rss,vsz,peaknch,peakcpu,peakmem,peakrss,peakvsz,avgnch,avgcpu,avgmem,avgrss,avgvsz,procname(inputpid))
			else printf("%s PID=%s> NCh=%s %CPU=%.1f %MEM=%.1f RSS=%.1fM VSZ=%.1fM PkNCh=%s Pk%CPU=%.1f Pk%MEM=%.1f PkRSS=%.1fM PkVSZ=%.1fM AvgNCh=%s Avg%CPU=%.1f Avg%MEM=%.1fM AvgRSS=%.1fM AvgVSZ=%.1fM (%s)\n",strftime("%H:%M:%S",systime()),inputpid,nch,cpu,mem,rss/1024,vsz/1024,peaknch,peakcpu,peakmem,peakrss/1024,peakvsz/1024,avgnch,avgcpu,avgmem,avgrss/1024,avgvsz/1024,procname(inputpid))
		}
		else
		{
			if (nrMEAS==1 || nrMEAS%10==0) printf("PID=%s (%s)\n%8s %5s> %3s %5s %5s %6s %6s %3s %5s %5s %6s %6s %3s %5s %5s %6s %6s\n",inputpid,procname(inputpid),"Time","PID","nCh","%CPU","%MEM","RSS","VSZ","pCh","pCPU","pMEM","pRSS","pVSZ","aCh","aCPU","aMEM","aRSS","aVSZ")
			printf("%s %5s> %3s %5.1f %5.1f %5.1fM %5.1fM %3s %5.1f %5.1f %5.1fM %5.1fM %3s %5.1f %5.1f %5.1fM %5.1fM\n",strftime("%H:%M:%S",systime()),inputpid,nch,cpu,mem,rss/1024,vsz/1024,peaknch,peakcpu,peakmem,peakrss/1024,peakvsz/1024,avgnch,avgcpu,avgmem,avgrss/1024,avgvsz/1024)
		}
	}
	return 1
}
function procname(inputpid)
{
	return gensub(/^ *[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +/,"",1,procinfo[inputpid])
}
function procsize(inputpid,what,   array)
{
	split(gensub(/^ */,"",1,procinfo[inputpid]),array," ")
	if (what=="add")
	{
		nch++
		cpu+=array[4]
		mem+=array[5]
		rss+=array[6]
		vsz+=array[7]
		totnch++
		totcpu+=array[4]
		totmem+=array[5]
		totrss+=array[6]
		totvsz+=array[7]
	}
	else if (what=="reset")
	{
		nch=0
		cpu=array[4]
		mem=array[5]
		rss=array[6]
		vsz=array[7]
	}	
	#to make these numbers as integers, otherwise they may get written with exponential when large numbers	
	rss+=0
	vsz+=0
	totrss+=0
	totvsz+=0
}
function find_procchildren(inputpid,  array,i,last,w,k,word,comline)
{
	last=split(procchildren[inputpid],array,",")
	for (i=1;i<=last;i++)
	{
		if (monitor == 0 || monitor == 2) 
		{
			if (monitor == 0) 
			{
				w=split(procinfo[array[i]],word," ")
				comline=""
				for (k=9;k<=w;k++) comline=comline word[k]" "
				printf("%5s  %5s  %-8s  %4.1f  %4.1f  %5.1fM  %5.1fM  %12s  %s\n",word[1],word[2],word[3],word[4],word[5],word[6]/1024,word[7]/1024,word[8],comline)
				#print procinfo[array[i]]
			}
			procchildren_list=procchildren_list","array[i]
		}
		procsize(array[i],"add")
		find_procchildren(array[i])
	}
}
function toseconds(string,   array,tot,last,i,j,val)
{
	#385-03:06:02 
	#    03:45:42 
	#       23:57 
	last=split(string,array,"-")
	if (last==2) tot=array[1]*86400
	last=split(array[last],array,":")
	for (i=last;i>=1;i--) val[++j]=array[i]
	tot+=val[1]+val[2]*60+val[3]*3600
	return tot
}
'
}

if [[ $# -eq 1 ]] ; then
	pstool $1
else
	print_usage
fi

