I wrote this little Multi-Processing Driver code because I needed something that would drive a large machine to capacity during a DUL (Oracle Data Un-Loader) engagement. I had a huge machine to work with, but a single DUL command would only us a fraction of the machine. I needed to run MANY of those DUL commands at the same time to consume the machine. This little piece of code was the result and it saved HOURS and DAYS of time on that engagement alone. The customer had 5,000 users waiting to get that database back, so time was critical! There are just 3 files as shown below…
- Initialization file
- command file
- MP Driver code
Initialization file (inifile)
The code reads the initiation file periodically, so you can change it mid-stream. The main control is PARALLELISM, so you can increase or decrease the degree of parallelism as the code is running. If you have enough resources available on the machine, turn it up. If the machine is struggling to keep up with the work, turn it down.
# #########################################################
# Initialization Parameter file for mpdriver
#
# Parameters are specfied one per line
#
# #########################################################
parallelism=4
debug=16
Command File (cmdfile)
The command file (cmdfile) is a list of commands that need to be run. The file needs to include a list of single-line commands. If you need more than 1 command, put those commands in a script and specify the script name in the cmdfile. The sample shows a bunch of Linux “sleep” commands, but you can put whatever you want into the cmdfile and the code will execute them in parallel.
# ##########################################
# These are comments
# ##########################################
sleep 10
sleep 11
sleep 12
sleep 13
sleep 14
sleep 15
sleep 16
sleep 17
sleep 18
sleep 19
sleep 20
sleep 21
sleep 22
sleep 23
sleep 24
MP Driver code (mpdriver)
This is a simple Korn Shell script (ksh) that can be run on virtually any Linux box. It spawns a sub-process to run each command in the cmdfile using the degree of parallelism specified in the initialization file.
#! /bin/ksh
# ###########################################################################
# Script : mpdriver
# Description : Multi Processing Driver
#
# Purpose : This script is a Multi-Processing driver. It reads a file
# containing one command per line and then executes those
# commands in parallel.
#
# The degree of parallelism is set in a configuration file.
# The parallelism can be changed on the fly if necessary
# by simply updating the configuration file.
#
#
# ###########################################################################
# Change Log:
#
# Date Description Who
# --------- ----------------------------------------------- --------------
# 20-MAY-2002 Initial creation C. Craft
# 17-JAN-2010 PID capture problem fixed in ExecuteThread C. Craft
#
# ###########################################################################
#----------------------------------------------------------------------------
# Name: AbendCleanUp
#----------------------------------------------------------------------------
function AbendCleanUp
{
print "abend Detected"
CleanUp
exit 0
} # End AbendCleanUp
#----------------------------------------------------------------------------
# Name: ShowUsage
#----------------------------------------------------------------------------
function ShowUsage
{
print "Usage: mpdriver [-c <command file>] [-i <init file>] [-d 0|16]"
CleanUp
exit 0
} # End ShowUsage
#----------------------------------------------------------------------------
# Name: CleanUp
#----------------------------------------------------------------------------
function CleanUp
{
print "Executing Generic Cleanup Function"
if [ ! -d ${TmpDir} ]
then
echo "Temp Directory is not a directory!"
CleanUp
fi
rm $TmpDir/*
rmdir $TmpDir
exit 0
} # End CleanUp
#----------------------------------------------------------------------------
# Name: RunCmds
# Desc: This function reads the command file and calls a routine to
# execute each command.
#----------------------------------------------------------------------------
function RunCmds
{
#---------------------------------
# Read Command File & Run Commands
#---------------------------------
typeset i j TotalWaitTime LoopCnt
ThreadCnt=1
typeset CmdCnt
let CmdCnt=0
let ThreadCnt=0
cat $cmdfile |while read Line
do
commentchar=`echo ${Line}|cut -c 1`
if [ ${commentchar} = '#' ]
then
if [ $debug -eq 16 ]
then
print "Found Comment in cmdfile: ${Line}"
fi
else
let CmdCnt=${CmdCnt}+1
CmdLine=${Line}
AllocThread
fi
done
# Wait for all the child processes to complete.
# CheckActiveThreads
CleanUp
return
} # End of RunCmds
#----------------------------------------------------------------------------
# Name: AllocThread
# Desc: Allocate a thread and then execute the command.
# If a thread is not available, wait for one.
#----------------------------------------------------------------------------
function AllocThread
{
if [ $debug -eq 16 ]
then
print "executing AllocThread"
print "Current Thread Count is ${ThreadCnt}"
fi
SlotFound='N'
while [ ${SlotFound} = 'N' ]
do
let Parallelism=`cat ${inifile}|grep parallelism|cut -f2 -d'='`
let debug=`cat ${inifile}|grep debug|cut -f2 -d'='`
if [ $debug -ne 0 -a $debug -ne 16 ]
then
print "debug must be 0 or 16"
print "reverting to debug 0"
debug=0
fi
if [ $debug -eq 16 ]
then
print "Current Parallelism is ${Parallelism}"
fi
if [ ${Parallelism} -gt ${ThreadCnt} ]
then
if [ $debug -eq 16 ]
then
print "Thread count is less than Parallelism"
fi
let ThreadCnt=${ThreadCnt}+1
let ThreadId=${ThreadCnt}
SlotFound='Y'
print "Allocating new thread slot. Thread count increased to ${ThreadCnt}."
ExecuteThread
else
ThreadId=1
while [ ${ThreadId} -le ${Parallelism} -a ${SlotFound} = 'N' ]
do
ps -p ${ThreadPid[${ThreadId}]}>/dev/null 2>&1
if [ $? -eq 0 ]
then
if [ $debug -eq 16 ]
then
print "Thread ${ThreadId} is still active"
fi
else
print "Open thread slot detected. Spawning new process for slot ${ThreadId}."
SlotFound='Y'
ExecuteThread
fi
if [ $debug -eq 16 ]
then
print "keep waiting for an open thread"
fi
let ThreadId=${ThreadId}+1
done
if [ ${SlotFound} = 'N' ]
then
if [ $debug -eq 16 ]
then
print "Sleeping to await available thread"
fi
sleep 4
fi
fi
done
return
} # End of AllocThread
#----------------------------------------------------------------------------
# Name: ExecuteThread
# Desc: Execute the thread in the background.
#
# 17-JAN-2010...
# Added 1/10th second wait for thread to spwan, wasn't capturing PID in
# some cases. Also changed the "let" command because that wouldn't
# reliably capture the PID on OEL5 (only on Thread #12 for some reason).
#----------------------------------------------------------------------------
function ExecuteThread
{
typeset Script ThreadOut ThreadTmp
Script="${TmpDir}/${ShellName}_${BackupId}_${ThreadId}.ksh"
ThreadOut="${TmpDir}/${ShellName}_${BackupId}_${ThreadId}.out"
ThreadTmp="${TmpDir}/${ShellName}_${BackupId}_${ThreadId}.tmp"
ThreadLog=${TmpDir}/thread_${ThreadId}.log
if [ $debug -eq 16 ]
then
print "executing ExecuteThread"
print "Thread ID is ${ThreadId}"
print "Thread Log is ${ThreadLog}"
fi
#-------------------------------------------
# Dynamically build the script
#-------------------------------------------
echo '---------------------------------------------------------------'
print "Executing command at: `date`"
print "cmd is: ${CmdLine}"
print "echo Started >> ${ThreadLog}" > ${Script}
print "${CmdLine}" >> ${Script}
print "perl -e 'select(undef,undef,undef,.1)'" >> ${Script}
print "echo SUCCESS >> ${ThreadLog}" >> ${Script}
#-------------------------------------------
# Spawn sub-shell and capture PID
#-------------------------------------------
chmod 740 ${Script}
${Script} > ${ThreadOut} 2>&1 &
# let ThreadPid[${ThreadId}]=$!
ThreadPid[${ThreadId}]=$!
ThreadFile[${ThreadId}]="${FileName}"
return
} # End of ExecuteThread
#############################################################################
# MAIN DRIVER
#############################################################################
# If we die unexpectedly, try to perform some cleanup.
trap 'Signal=Y; AbendCleanUp' HUP INT QUIT ABRT TERM
# Initialize variables and setup env.
OSName=`uname -s`
ShellName=`basename $0`
StartDate=`date +"%m%d"`
StartTime=`date +"%H%M"`
typeset -RZ4 ThreadId
typeset -RZ4 Parallelism
typeset -RZ4 BkupDirThrd
let TotalSize=0
let ThreadId=0
let TbsCnt=0
let FileCnt=0
let NoRestart=0
TmpBase=/tmp
WaitTime=10
MaxWaitTime=300
MaxParallelism=32
# Get Command Line Arguments
while getopts c:i: OPT
do
case $OPT in
c) # command file
let cmdfile=${OPTARG}
;;
i) # init file
let inifile=${OPTARG}
;;
*) # Invalid option
print "Invalid Option: ${OPT}"
ShowUsage
exit 1
;;
esac
done
if [ -z $cmdfile ]
then
cmdfile=cmdfile
fi
if [ -z $inifile ]
then
inifile=inifile
fi
let debug=`cat ${inifile}|grep debug|cut -f2 -d'='`
if [ $debug -ne 0 -a $debug -ne 16 ]
then
echo "You selected debug level ${debug}"
echo "debug must be 0 or 16"
echo "Debug is configured in the inifile"
ShowUsage
exit
fi
TmpDir=${TmpBase}/mpdriver$$
mkdir $TmpDir
if [ $? -ne 0 ]
then
echo "Failed to make Temp Directory"
CleanUp
fi
if [ ! -d ${TmpDir} ]
then
echo "Temp Directory is not a directory!"
CleanUp
fi
print "Multi Processing Driver"
print "Starting at `date`"
RunCmds
exit 0