1313# This file contains a couple of utility functions for reuse
1414# in shell job scripts (such as on the GRID).
1515# In order to use these functions in scripts, this file needs to be
16- # simply sourced into the target script.
16+ # simply sourced into the target script. The script needs bash versions > 4
1717
18+ # TODOs:
19+ # -harmonize use of bc/awk for calculations
20+ # -harmonize coding style for variables
1821
1922o2_cleanup_shm_files () {
2023 # check if we have lsof (otherwise we do nothing)
@@ -91,6 +94,8 @@ taskwrapper_cleanup_handler() {
9194# (until DPL offers signal handling and automatic shutdown)
9295# - possibility to provide user hooks for "start" and "failure"
9396# - possibility to skip (jump over) job alltogether
97+ # - possibility to define timeout
98+ # - possibility to control/limit the CPU load
9499taskwrapper () {
95100 local logfile=$1
96101 shift 1
@@ -141,7 +146,7 @@ taskwrapper() {
141146 finalcommand=" TIME=\" #walltime %e\" ${O2_ROOT} /share/scripts/monitor-mem.sh ${TIMECOMMAND} './${SCRIPTNAME} '"
142147 fi
143148 echo " Running: ${finalcommand} " > ${logfile}
144- eval ${finalcommand} >> ${logfile} 2>&1 &
149+ eval ${finalcommand} >> ${logfile} 2>&1 & disown
145150
146151 # THE NEXT PART IS THE SUPERVISION PART
147152 # get the PID
@@ -151,7 +156,9 @@ taskwrapper() {
151156 trap " taskwrapper_cleanup_handler ${PID} SIGTERM" SIGTERM
152157
153158 cpucounter=1
159+ NLOGICALCPUS=$( getNumberOfLogicalCPUCores)
154160
161+ reduction_factor=1
155162 while [ 1 ]; do
156163 # We don't like to see critical problems in the log file.
157164
@@ -202,21 +209,83 @@ taskwrapper() {
202209 ps -p $PID > /dev/null
203210 [ $? == 1 ] && break
204211
205- if [ " ${JOBUTILS_MONITORCPU} " ]; then
212+ if [ " ${JOBUTILS_MONITORCPU} " ] || [ " ${JOBUTILS_LIMITLOAD} " ]; then
213+ # NOTE: The following section is "a bit" compute intensive and currently not optimized
214+ # A careful evaluation of awk vs bc or other tools might be needed -- or a move to a more
215+ # system oriented language/tool
216+
217+ for p in $limitPIDs ; do
218+ wait ${p}
219+ done
220+
206221 # get some CPU usage statistics per process --> actual usage can be calculated thereafter
207- for p in $( childprocs ${PID} ) ; do
208- total=` awk ' BEGIN{s=0}/cpu /{for (i=1;i<=NF;i++) s+=$i;} END {print s}' /proc/stat`
209- utime=` awk ' //{print $14}' /proc/${p} /stat 2> /dev/null`
210- stime=` awk ' //{print $15}' /proc/${p} /stat 2> /dev/null`
211- name=` awk ' //{print $2}' /proc/${p} /stat 2> /dev/null`
212- echo " ${cpucounter} ${p} ${total} ${utime} ${stime} ${name} " >> ${logfile} _cpuusage
222+ total=` awk ' BEGIN{s=0}/cpu /{for (i=1;i<=NF;i++) s+=$i;} END {print s}' /proc/stat`
223+ previous_total=${current_total}
224+ current_total=${total}
225+ # quickly fetch the data
226+ childpids=$( childprocs ${PID} )
227+
228+ for p in $childpids ; do
229+ while read -r name utime stime; do
230+ echo " ${cpucounter} ${p} ${total} ${utime} ${stime} ${name} " >> ${logfile} _cpuusage
231+ previous[$p ]=${current[$p]}
232+ current[$p ]=${utime}
233+ name[$p ]=${name}
234+ done <<< $( awk ' //{print $2" "$14" "$15}' /proc/${p} /stat 2> /dev/null)
213235 done
236+ # do some calculations based on the data
237+ totalCPU=0 # actual CPU load measured
238+ totalCPU_unlimited=0 # extrapolated unlimited CPU load
239+ line=" "
240+ for p in $childpids ; do
241+ C=${current[$p]}
242+ P=${previous[$p]}
243+ CT=${total}
244+ PT=${previous_total}
245+ # echo "${p} : current ${C} previous ${P} ${CT} ${PT}"
246+ thisCPU[$p ]=$( awk -v " c=${C} " -v " p=${P} " -v " ct=${CT} " -v " pt=${PT} " -v " ncpu=${NLOGICALCPUS} " ' BEGIN { print 100.*ncpu*(c-p)/(ct-pt); }' )
247+ line=" ${line} $p :${thisCPU[$p]} "
248+ totalCPU=$( awk -v " t=${totalCPU} " -v " this=${thisCPU[$p]} " ' BEGIN { print (t + this); }' )
249+ previousfactor=1
250+ [ ${waslimited[$p]} ] && previousfactor=${reduction_factor}
251+ totalCPU_unlimited=$( awk -v " t=${totalCPU_unlimited} " -v " this=${thisCPU[$p]} " -v f=" ${previousfactor} " ' BEGIN { print (t + this/f); }' )
252+ # echo "CPU last time window ${p} : ${thisCPU[$p]}"
253+ done
254+
255+ echo " ${line} "
256+ echo " ${cpucounter} totalCPU = ${totalCPU} -- without limitation ${totalCPU_unlimited} "
257+ # We can check if the total load is above a resource limit
258+ # And take corrective actions if we extend by 10%
259+ limitPIDs=" "
260+ unset waslimited
261+ if (( $(echo "${totalCPU_unlimited} > 1 .1 * ${JOBUTILS_LIMITLOAD} " | bc - l) )) ; then
262+ # we reduce each pid proportionally for the time until the next check and record the reduction factor in place
263+ oldreduction=${reduction_factor}
264+ reduction_factor=$( awk -v limit=" ${JOBUTILS_LIMITLOAD} " -v cur=" ${totalCPU_unlimited} " ' BEGIN{ print limit/cur;}' )
265+ echo " APPLYING REDUCTION = ${reduction_factor} "
266+
267+ for p in $childpids ; do
268+ cpulim=$( awk -v a=" ${thisCPU[${p}]} " -v newr=" ${reduction_factor} " -v oldr=" ${oldreduction} " ' BEGIN { r=(a/oldr)*newr; print r; if(r > 0.05) {exit 0;} exit 1; }' )
269+ if [ $? = " 0" ]; then
270+ # we only apply to jobs above a certain threshold
271+ echo " Setting CPU lim for job ${p} / ${name[$p]} to ${cpulim} " ;
272+
273+ timeout ${JOBUTILS_WRAPPER_SLEEP} ${O2_ROOT} /share/scripts/cpulimit -l ${cpulim} -p ${p} > /dev/null 2> /dev/null & disown
274+ proc=$!
275+ limitPIDs=" ${limitPIDs} ${proc} "
276+ waslimited[$p ]=1
277+ fi
278+ done
279+ else
280+ echo " RESETING REDUCTION = 1"
281+ reduction_factor=1.
282+ fi
214283 let cpucounter=cpucounter+1
215284 fi
216285
217286 # a good moment to check for jobs timeout (or other resources)
218287 if [ " $JOBUTILS_JOB_TIMEOUT " ]; then
219- $( awk -v S=" ${SECONDS} " -v T=" ${JOBUTILS_JOB_TIMEOUT} " -v START=" ${STARTTIME} " ' //{} END {if((S-START)>T){exit 1;} exit 0;}' < /dev/null )
288+ $( awk -v S=" ${SECONDS} " -v T=" ${JOBUTILS_JOB_TIMEOUT} " -v START=" ${STARTTIME} " ' BEGIN {if((S-START)>T){exit 1;} exit 0;}' )
220289 if [ " $? " = " 1" ]; then
221290 echo " task timeout reached .. killing all processes" ;
222291 taskwrapper_cleanup $PID SIGKILL
@@ -230,7 +299,7 @@ taskwrapper() {
230299 fi
231300
232301 # sleep for some time (can be customized for power user)
233- sleep ${JOBUTILS_WRAPPER_SLEEP:- 5 }
302+ sleep ${JOBUTILS_WRAPPER_SLEEP:- 10 }
234303 done
235304
236305 # wait for PID and fetch return code
@@ -281,3 +350,11 @@ getNumberOfPhysicalCPUCores() {
281350 echo " ${N} "
282351}
283352
353+ getNumberOfLogicalCPUCores () {
354+ if [ " $( uname) " == " Darwin" ]; then
355+ echo $( sysctl -n hw.logicalcpu)
356+ else
357+ # Do something under GNU/Linux platform
358+ echo $( grep " processor" /proc/cpuinfo | wc -l)
359+ fi
360+ }
0 commit comments