blob: d29168f2f50407487aefc7b0b74d522534743081 [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
14DEVSTACK_PARALLEL=$(trueorfalse False DEVSTACK_PARALLEL)
15_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
60 shift
61 set -o xtrace
62 if $* >${DEST}/async/${name}.log 2>&1; then
63 rc=0
64 set +o xtrace
65 async_log "$name" "finished successfully"
66 else
67 rc=$?
68 set +o xtrace
69 async_log "$name" "FAILED with rc $rc"
70 fi
71 iniset ${DEST}/async/${name}.ini job end_time $(date "+%s%3N")
72 return $rc
73}
74
75# Run something async. Takes a symbolic name and a list of arguments of
76# what to run. Ideally this would be rarely used and async_runfunc() would
77# be used everywhere for readability.
78#
79# This spawns the work in a background worker, records a "future" to be
80# collected by a later call to async_wait()
81function async_run {
82 local xtrace
83 xtrace=$(set +o | grep xtrace)
84 set +o xtrace
85
86 local name="$1"
87 shift
88 local inifile=${DEST}/async/${name}.ini
89
90 touch $inifile
91 iniset $inifile job command "$*"
92 iniset $inifile job start_time $(date +%s%3N)
93
94 if [[ "$DEVSTACK_PARALLEL" = "True" ]]; then
95 async_inner $name $* &
96 iniset $inifile job pid $!
97 async_log "$name" "running: %command"
98 $xtrace
99 else
100 iniset $inifile job pid "self"
101 async_log "$name" "Running synchronously: %command"
102 $xtrace
103 $*
104 return $?
105 fi
106}
107
108# Shortcut for running a shell function async. Uses the function name as the
109# async name.
110function async_runfunc {
111 async_run $1 $*
112}
113
114# Wait for an async future to complete. May return immediately if already
115# complete, or of the future has already been waited on (avoid this). May
116# block until the future completes.
117function async_wait {
118 local xtrace
119 xtrace=$(set +o | grep xtrace)
120 set +o xtrace
121
122 local pid rc running inifile runtime
123 rc=0
124 for name in $*; do
125 running=$(ls ${DEST}/async/*.ini 2>/dev/null | wc -l)
126 inifile="${DEST}/async/${name}.ini"
127
128 if pid=$(async_pidof "$name"); then
129 async_log "$name" "Waiting for completion of %command" \
130 "($running other jobs running)"
131 time_start async_wait
132 if [[ "$pid" != "self" ]]; then
133 # Do not actually call wait if we ran synchronously
134 if wait $pid; then
135 rc=0
136 else
137 rc=$?
138 fi
139 cat ${DEST}/async/${name}.log
140 fi
141 time_stop async_wait
142 local start_time
143 local end_time
144 start_time=$(iniget $inifile job start_time)
145 end_time=$(iniget $inifile job end_time)
146 _async_incr_bg_time $(($end_time - $start_time))
147 runtime=$((($end_time - $start_time) / 1000))
148 async_log "$name" "finished %command with result" \
149 "$rc in $runtime seconds"
150 rm -f $inifile
151 if [ $rc -ne 0 ]; then
152 echo Stopping async wait due to error: $*
153 break
154 fi
155 else
156 # This could probably be removed - it is really just here
157 # to help notice if you wait for something by the wrong
158 # name, but it also shows up for things we didn't start
159 # because they were not enabled.
160 echo Not waiting for async task $name that we never started or \
161 has already been waited for
162 fi
163 done
164
165 $xtrace
166 return $rc
167}
168
169# Check for uncollected futures and wait on them
170function async_cleanup {
171 local name
172
173 if [[ "$DEVSTACK_PARALLEL" != "True" ]]; then
174 return 0
175 fi
176
177 for inifile in $(find ${DEST}/async -name '*.ini'); do
178 name=$(basename $pidfile .ini)
179 echo "WARNING: uncollected async future $name"
180 async_wait $name || true
181 done
182}
183
184# Make sure our async dir is created and clean
185function async_init {
186 local async_dir=${DEST}/async
187
188 # Clean any residue if present from previous runs
189 rm -Rf $async_dir
190
191 # Make sure we have a state directory
192 mkdir -p $async_dir
193}
194
195function async_print_timing {
196 local bg_time_minus_wait
197 local elapsed_time
198 local serial_time
199 local speedup
200
201 if [[ "$DEVSTACK_PARALLEL" != "True" ]]; then
202 return 0
203 fi
204
205 # The logic here is: All the background task time would be
206 # serialized if we did not do them in the background. So we can
207 # add that to the elapsed time for the whole run. However, time we
208 # spend waiting for async things to finish adds to the elapsed
209 # time, but is time where we're not doing anything useful. Thus,
210 # we substract that from the would-be-serialized time.
211
212 bg_time_minus_wait=$((\
213 ($_ASYNC_BG_TIME - ${_TIME_TOTAL[async_wait]}) / 1000))
214 elapsed_time=$(($(date "+%s") - $_TIME_BEGIN))
215 serial_time=$(($elapsed_time + $bg_time_minus_wait))
216
217 echo
218 echo "================="
219 echo " Async summary"
220 echo "================="
221 echo " Time spent in the background minus waits: $bg_time_minus_wait sec"
222 echo " Elapsed time: $elapsed_time sec"
223 echo " Time if we did everything serially: $serial_time sec"
224 echo " Speedup: " $(echo | awk "{print $serial_time / $elapsed_time}")
225}