I have some programs involved in embedded systems updating, which need to be run in sequence, and I need to report progress to a separate system.
The phases are:
After that, the machine is rebooted and the alternate disk becomes the active one.
At the moment, we report 0% initially, 5% at the end of validation, 45% at the end of unpacking and 90% at the end of installing. The 100% mark is reported following reboot when the new image starts running.
Now, while I'm more than happy with this method, the client wants to see a more fine-grained reporting of update status. We have let them know in no uncertain terms that any such reporting will be mostly artificial but they think it's necessary to keep their customers happy.
So I'm looking for a way to report progress based on rough time while still having the long-running tasks go off and do their work.
We know that the entire installation process takes about two minutes from start to rebooting, with the times spent in each phase apportioned as per the given percentages.
One way to do this in bash
is to run a separate process to do the progress reporting and give it enough information to report successfully. This would involve:
The first two are obvious, they provide the lower and upper bounds for what get's reported.
The other two allow you to provide a ratio for reporting. For example, an increase of 6% per second would use the values (delta = 6, cycle = 1)
and, every second, 6% would be added.
For a slower step, say 0.25% every second, you would provide (delta = 1, cycle = 4)
and, every four seconds, 1% will be added. You don't actually report the sub-second progress, it's just that it will average out to be that over time.
So, how is this done? First, let's provide a simple function for reporting the progress. In your situation, this should be replaced by whatever is used to communicate with that "separate system" you mention. The following code shows how to do this:
#!/usr/bin/env bash
doProgress() {
# Simply echo progress using whatever was passed. Date is included
# for debugging purposes.
echo "$(date +%H:%M:%S): $1%"
}
The "meat" of the solution is the backgroundReporter()
function, the one that will call doProgress()
based on the four parameters mentioned earlier:
backgroundReporter() {
# Get the four parameters.
currVal=$1 ; maxVal=$2 ; deltaVal=$3 ; timeGap=$4
# If signalled to stop, output maximum percentage then exit.
# Note no output if you've already reached the maximum.
trap "[[ \${maxVal} -ne \${lastVal} ]] && doProg \${maxVal} ; exit" HUP
# Infinite loop until signalled.
while : ; do
# Wait for the duration, save current percentage, and
# calculate new percntage (capped at maximum).
sleep ${timeGap}
lastVal=${currVal}
(( currVal = (currVal + deltaVal > maxVal) ? maxVal : currVal + deltaVal ))
# Log only if it's changed.
[[ ${currVal} -ne ${lastVal} ]] && doProg ${currVal}
done
}
The only tricky bit there is the signal handling. The caller that runs this function as a background task is responsible for sending the signal to it once the phase is finished.
And, of course, some rudimentary test cases to show this in action. First, we need to make sure that, on exit, we kill the child process running backgroundReporter()
. The command to do this will be :
(a simple no-op) when it's not running and kill -HUP <pid>
when it is running:
killCmd=":" ; trap "${killCmd}" EXIT
Then we report an initial progress of zero and begin the three phases. They're shorter here than what the question specified since this is for demonstration purposes only:
doProg 0
# 0 -> 10% at 4%/sec, phase takes six seconds.
bgReport 0 10 4 1 & killCmd="kill -HUP $!" ; sleep 6 ; ${killCmd} ; killCmd=":" ; wait
# 10 -> 20% at 1%/2secs, phase takes six seconds.
bgReport 10 20 1 2 & killCmd="kill -HUP $!" ; sleep 6 ; ${killCmd} ; killCmd=":" ; wait
# 20 -> 100% at 30%/sec, phase takes five seconds.
bgReport 20 100 30 1 & killCmd="kill -HUP $!" ; sleep 5 ; ${killCmd} ; killCmd=":" ; wait
And, as you can see from a sample run, the three phases work out much as you'd expect (comments on right are mine):
13:15:29: 0%
13:15:30: 4% 4% per sec
13:15:31: 8%
13:15:32: 10% capped at 10% for remainder of 6-sec slot
13:15:37: 11% goes up 1% per 2 secs
13:15:39: 12%
13:15:41: 20% phase finishes, jumps immediately to max
13:15:42: 50% goes up 30% per second
13:15:43: 80%
13:15:44: 100% but capped at 100%
For the actual times given, two minutes with the phases being 5% end validation, 45% end unpacking and 90% end installing, the following would be more appropriate (phase are slightly modified so the ratio is easier to do):
doProg 0
# Validation 0 -> 5%, 5 steps in 6 seconds (~ 1%/1s).
bgReport 0 5 1 1 & killCmd="kill -HUP $!"
validate
${killCmd} ; killCmd=":" ; wait
# Unpacking 5 -> 45%, 40 steps in 50 seconds (4%/5s).
bgReport 5 45 4 5 & killCmd="kill -HUP $!"
unpack
${killCmd} ; killCmd=":" ; wait
# Installing 45 -> 95%, 50 steps in 64 seconds (~ 5%/6s)
bgReport 45 95 5 6 & killCmd="kill -HUP $!"
install
${killCmd} ; killCmd=":" ; wait
With those ratios, you're guaranteed to get a progress update every (at most) six seconds, unlike the original method where it would sit at 45% for a good minute then jump immediately to 95%.