blob: 11bcdfa39e1715f58c45221e24ffb4fefea30c59 [file] [log] [blame]
Dan Smith30d9bf92021-01-19 12:10:52 -08001#!/bin/bash
2#
3# Symbolic asynchronous tasks for devstack
4#
5# Usage:
6#
7# async_runfunc my_shell_func foo bar baz
8#
9# ... do other stuff ...
10#
11# async_wait my_shell_func
12#
13
Dan Smith84b328c2021-03-26 07:17:42 -070014DEVSTACK_PARALLEL=$(trueorfalse True DEVSTACK_PARALLEL)
Dan Smith30d9bf92021-01-19 12:10:52 -080015_ASYNC_BG_TIME=0
16
17# Keep track of how much total time was spent in background tasks
18# Takes a job runtime in ms.
19function _async_incr_bg_time {
20 local elapsed_ms="$1"
21 _ASYNC_BG_TIME=$(($_ASYNC_BG_TIME + $elapsed_ms))
22}
23
24# Get the PID of a named future to wait on
25function async_pidof {
26 local name="$1"
27 local inifile="${DEST}/async/${name}.ini"
28
29 if [ -f "$inifile" ]; then
30 iniget $inifile job pid
31 else
32 echo 'UNKNOWN'
33 return 1
34 fi
35}
36
37# Log a message about a job. If the message contains "%command" then the
38# full command line of the job will be substituted in the output
39function async_log {
40 local name="$1"
41 shift
42 local message="$*"
43 local inifile=${DEST}/async/${name}.ini
44 local pid
45 local command
46
47 pid=$(iniget $inifile job pid)
48 command=$(iniget $inifile job command | tr '#' '-')
49 message=$(echo "$message" | sed "s#%command#$command#g")
50
51 echo "[Async ${name}:${pid}]: $message"
52}
53
54# Inner function that actually runs the requested task. We wrap it like this
55# just so we can emit a finish message as soon as the work is done, to make
56# it easier to find the tracking just before an error.
57function async_inner {
58 local name="$1"
59 local rc
Dan Smithaa5c3872021-04-14 14:27:32 -070060 local fifo=${DEST}/async/${name}.fifo
Dan Smith30d9bf92021-01-19 12:10:52 -080061 shift
62 set -o xtrace
63 if $* >${DEST}/async/${name}.log 2>&1; then
64 rc=0
65 set +o xtrace
66 async_log "$name" "finished successfully"
67 else
68 rc=$?
69 set +o xtrace
70 async_log "$name" "FAILED with rc $rc"
71 fi
72 iniset ${DEST}/async/${name}.ini job end_time $(date "+%s%3N")
Dan Smithaa5c3872021-04-14 14:27:32 -070073 # Block on the fifo until we are signaled to exit by the main process
74 cat $fifo
Dan Smith30d9bf92021-01-19 12:10:52 -080075 return $rc
76}
77
78# Run something async. Takes a symbolic name and a list of arguments of
79# what to run. Ideally this would be rarely used and async_runfunc() would
80# be used everywhere for readability.
81#
82# This spawns the work in a background worker, records a "future" to be
83# collected by a later call to async_wait()
84function async_run {
85 local xtrace
86 xtrace=$(set +o | grep xtrace)
87 set +o xtrace
88
89 local name="$1"
90 shift
91 local inifile=${DEST}/async/${name}.ini
Dan Smithaa5c3872021-04-14 14:27:32 -070092 local fifo=${DEST}/async/${name}.fifo
Dan Smith30d9bf92021-01-19 12:10:52 -080093
94 touch $inifile
95 iniset $inifile job command "$*"
96 iniset $inifile job start_time $(date +%s%3N)
97
98 if [[ "$DEVSTACK_PARALLEL" = "True" ]]; then
Dan Smithaa5c3872021-04-14 14:27:32 -070099 mkfifo $fifo
Dan Smith30d9bf92021-01-19 12:10:52 -0800100 async_inner $name $* &
101 iniset $inifile job pid $!
102 async_log "$name" "running: %command"
103 $xtrace
104 else
105 iniset $inifile job pid "self"
106 async_log "$name" "Running synchronously: %command"
107 $xtrace
108 $*
109 return $?
110 fi
111}
112
113# Shortcut for running a shell function async. Uses the function name as the
114# async name.
115function async_runfunc {
116 async_run $1 $*
117}
118
119# Wait for an async future to complete. May return immediately if already
120# complete, or of the future has already been waited on (avoid this). May
121# block until the future completes.
122function async_wait {
123 local xtrace
124 xtrace=$(set +o | grep xtrace)
125 set +o xtrace
126
Dan Smithaa5c3872021-04-14 14:27:32 -0700127 local pid rc running inifile runtime fifo
Dan Smith30d9bf92021-01-19 12:10:52 -0800128 rc=0
129 for name in $*; do
130 running=$(ls ${DEST}/async/*.ini 2>/dev/null | wc -l)
131 inifile="${DEST}/async/${name}.ini"
Dan Smithaa5c3872021-04-14 14:27:32 -0700132 fifo=${DEST}/async/${name}.fifo
Dan Smith30d9bf92021-01-19 12:10:52 -0800133
134 if pid=$(async_pidof "$name"); then
135 async_log "$name" "Waiting for completion of %command" \
136 "($running other jobs running)"
137 time_start async_wait
138 if [[ "$pid" != "self" ]]; then
Dan Smithaa5c3872021-04-14 14:27:32 -0700139 # Signal the child to go ahead and exit since we are about to
140 # wait for it to collect its status.
141 echo "Signaling exit"
142 echo WAKEUP > $fifo
143 echo "Signaled"
Dan Smith30d9bf92021-01-19 12:10:52 -0800144 # Do not actually call wait if we ran synchronously
145 if wait $pid; then
146 rc=0
147 else
148 rc=$?
149 fi
150 cat ${DEST}/async/${name}.log
Dan Smithaa5c3872021-04-14 14:27:32 -0700151 rm -f $fifo
Dan Smith30d9bf92021-01-19 12:10:52 -0800152 fi
153 time_stop async_wait
154 local start_time
155 local end_time
156 start_time=$(iniget $inifile job start_time)
157 end_time=$(iniget $inifile job end_time)
158 _async_incr_bg_time $(($end_time - $start_time))
159 runtime=$((($end_time - $start_time) / 1000))
160 async_log "$name" "finished %command with result" \
161 "$rc in $runtime seconds"
162 rm -f $inifile
163 if [ $rc -ne 0 ]; then
164 echo Stopping async wait due to error: $*
165 break
166 fi
167 else
168 # This could probably be removed - it is really just here
169 # to help notice if you wait for something by the wrong
170 # name, but it also shows up for things we didn't start
171 # because they were not enabled.
172 echo Not waiting for async task $name that we never started or \
173 has already been waited for
174 fi
175 done
176
177 $xtrace
178 return $rc
179}
180
181# Check for uncollected futures and wait on them
182function async_cleanup {
183 local name
184
185 if [[ "$DEVSTACK_PARALLEL" != "True" ]]; then
186 return 0
187 fi
188
189 for inifile in $(find ${DEST}/async -name '*.ini'); do
190 name=$(basename $pidfile .ini)
191 echo "WARNING: uncollected async future $name"
192 async_wait $name || true
193 done
194}
195
196# Make sure our async dir is created and clean
197function async_init {
198 local async_dir=${DEST}/async
199
200 # Clean any residue if present from previous runs
201 rm -Rf $async_dir
202
203 # Make sure we have a state directory
204 mkdir -p $async_dir
205}
206
207function async_print_timing {
208 local bg_time_minus_wait
209 local elapsed_time
210 local serial_time
211 local speedup
212
213 if [[ "$DEVSTACK_PARALLEL" != "True" ]]; then
214 return 0
215 fi
216
217 # The logic here is: All the background task time would be
218 # serialized if we did not do them in the background. So we can
219 # add that to the elapsed time for the whole run. However, time we
220 # spend waiting for async things to finish adds to the elapsed
221 # time, but is time where we're not doing anything useful. Thus,
222 # we substract that from the would-be-serialized time.
223
224 bg_time_minus_wait=$((\
225 ($_ASYNC_BG_TIME - ${_TIME_TOTAL[async_wait]}) / 1000))
226 elapsed_time=$(($(date "+%s") - $_TIME_BEGIN))
227 serial_time=$(($elapsed_time + $bg_time_minus_wait))
228
229 echo
230 echo "================="
231 echo " Async summary"
232 echo "================="
233 echo " Time spent in the background minus waits: $bg_time_minus_wait sec"
234 echo " Elapsed time: $elapsed_time sec"
235 echo " Time if we did everything serially: $serial_time sec"
236 echo " Speedup: " $(echo | awk "{print $serial_time / $elapsed_time}")
237}