Skip to content

Commit 5093943

Browse files
committed
Anchor the reload on the probe's own progress instead of a fixed sleep.
The cps and lc probes now print one progress line per second, and the harness waits for a proven point (3 s and 200 requests by default) before sending the HUP, so the reload always lands inside traffic that can be quoted in the measured line. The probe duration grows to 180 s so a slow window no longer trips the liveness check.
1 parent e96a587 commit 5093943

4 files changed

Lines changed: 121 additions & 9 deletions

File tree

‎tests/integration/common/reload_checks.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ def validate(values):
4747
"PERF_THREADS": (1, 256),
4848
"PERF_CONNS": (1, 1024),
4949
"PERF_DURATION": (1, 600),
50+
"HUP_ANCHOR_SEC": (1, 600),
51+
"HUP_ANCHOR_COUNT": (1, 100000000),
5052
"SHUTDOWN_TIMEOUT": (0, 900)}
5153
for key, (low, high) in bounds.items():
5254
value = values[key]

‎tests/integration/common/reload_probes/http_probe.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,17 +202,43 @@ def guarded(target, *args):
202202
stats["workers_done"] += 1
203203
stats["workers_active"] += local.requests > 0
204204

205+
# One line per second so the harness can anchor the reload on proven
206+
# progress (the probe is really producing traffic) instead of a fixed
207+
# sleep after the process merely exists.
208+
def report_progress(prefix, key, fail_key):
209+
while not progress_done.wait(1.0):
210+
with lock:
211+
n = stats[key]
212+
f = stats[fail_key]
213+
print("%s elapsed=%.1f n=%d fail=%d" %
214+
(prefix, time.monotonic() - started, n, f), flush=True)
215+
205216
if mode == "stream":
206217
jobs = [(stream, i) for i in range(a.streams)]
207218
elif mode == "lc":
208219
jobs = [(traffic, "lc", True) for _ in range(a.conns)] + [(traffic, "fresh", False)]
209220
else:
210221
jobs = [(traffic, "fresh", False) for _ in range(a.threads if mode == "cps" else 1)]
222+
progress_done = threading.Event()
223+
progressor = None
224+
if mode == "cps":
225+
progressor = threading.Thread(target=report_progress, daemon=True,
226+
args=("CPS_PROGRESS", "fresh_n", "fresh_fail"))
227+
elif mode == "lc":
228+
progressor = threading.Thread(target=report_progress, daemon=True,
229+
args=("LC_PROGRESS", "reqs", "fail"))
230+
if progressor is not None:
231+
progressor.start()
232+
211233
workers = [threading.Thread(target=guarded, args=job) for job in jobs]
212234
for worker in workers:
213235
worker.start()
214236
for worker in workers:
215237
worker.join()
238+
239+
if progressor is not None:
240+
progress_done.set()
241+
progressor.join(timeout=2)
216242
details = " workers_expected=%d workers_done=%d workers_active=%d worker_errors=%d" % (
217243
len(workers), stats["workers_done"], stats["workers_active"], stats["worker_errors"])
218244
if mode == "lc":

‎tests/integration/common/reload_runtime.sh‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,44 @@ probe_running() {
109109
run_client "python3 -B '$REMOTE_DIR/reload_remote.py' running '$REMOTE_DIR' '$CURRENT_PROBE'" >/dev/null 2>&1
110110
}
111111

112+
# Anchor the reload on the probe's own progress instead of a fixed sleep
113+
# after launch: the probe prints one '<PATTERN> elapsed=.. n=..' line per
114+
# second, so the HUP can be sent at a moment that is proven to have traffic
115+
# (F-M4-7 wants ACTIVE connections for the drain verdict) and that can be
116+
# quoted afterwards. Prints the matching line, 1 = never reached the anchor.
117+
wait_probe_progress() { # pattern min_elapsed min_count timeout
118+
local pattern="$1" min_elapsed="$2" min_count="$3" seconds="$4"
119+
local output line until
120+
[ -n "$CURRENT_PROBE" ] || return 1
121+
until=$((SECONDS + seconds))
122+
while [ "$SECONDS" -lt "$until" ]; do
123+
# 'result' prints nothing until the job finishes (it returns 75
124+
# while running), so the live progress has to be read from the log.
125+
# Only the progress lines: a burst of PROBE_ERROR must not evict
126+
# them from a tail window and turn the anchor into a timeout.
127+
output=$(CLIENT_TIMEOUT=$((until - SECONDS)) run_client \
128+
"grep -a '^"$pattern"' '$REMOTE_DIR/$CURRENT_PROBE.log' 2>/dev/null | tail -n 5") \
129+
|| output=""
130+
line=$(printf '%s\n' "$output" | awk -v p="$pattern" \
131+
-v e="$min_elapsed" -v c="$min_count" '
132+
$1 == p {
133+
el = 0; nn = 0;
134+
for (i = 2; i <= NF; i++) {
135+
if ($i ~ /^elapsed=/) el = substr($i, 9) + 0;
136+
if ($i ~ /^n=/) nn = substr($i, 3) + 0;
137+
}
138+
if (el >= e && nn >= c) last = $0;
139+
}
140+
END { if (last != "") print last; }')
141+
if [ -n "$line" ]; then
142+
printf '%s\n' "$line"
143+
return 0
144+
fi
145+
sleep 1
146+
done
147+
return 1
148+
}
149+
112150
wait_client_summary() {
113151
local ignored_path="$1" pattern="$2" seconds="$3" output rc until
114152
[ -n "$CURRENT_PROBE" ] || return 1

‎tests/integration/test_graceful_reload.sh‎

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,21 @@ STREAM_DURATION=120
8080
# of do_hup_case(), so these defaults cannot change any other case.
8181
PERF_THREADS=1
8282
PERF_CONNS=12
83-
PERF_DURATION=110
83+
# The probe has to outlive the reload window: the worst observed window is
84+
# ~120 s (a stalled drain hits the 90 s deadline and G_old then quits), and
85+
# 'probe still running after the reload' is part of the verdict. Note that
86+
# this also widens that liveness margin (~105 s -> ~175 s); every rate is
87+
# n / PERF_DURATION, so baselines stay comparable, and the longer window is
88+
# stricter for the fail=0 style criteria.
89+
PERF_DURATION=180
90+
# HUP anchor: the reload is sent once the probe has proven this much
91+
# progress, instead of a fixed sleep after the process merely exists.
92+
# Measured: at 3 s the probe has already produced ~20k requests (proof of
93+
# active traffic) and the window drains in ~2 s; anchoring later (8 s,
94+
# ~55k requests) reproducibly strands a half-open entry in G_old and the
95+
# drain runs to its 90 s deadline (2/2 runs), so 3 s is the default.
96+
HUP_ANCHOR_SEC=3
97+
HUP_ANCHOR_COUNT=200
8498
KERNEL_NIC_IP=""
8599
# Runtime fault injection (FF_FAULT). Empty = production form; a non-empty name
86100
# requires a fault-injection build manifest (checked by reload_checks.verify-build).
@@ -108,6 +122,7 @@ EXCLUDED_CASES="rt13"
108122
ERRLOG=""
109123
PIDFILE=""
110124
STREAM_MD5=""
125+
HUP_ANCHOR_LINE=""
111126
HUP_SUMMARY="NA"
112127
HUP_DRAIN="NA"
113128
HUP_FWD="NA"
@@ -196,7 +211,9 @@ Usage: test_graceful_reload.sh -t <TARGET_IP> [options]
196211
--rte-fresh-min <min> /var/run/dpdk/rte freshness gate (default 10)
197212
--perf-threads <n> m4_cps.py threads for rt30 (default 1)
198213
--perf-conns <n> m4_lc.py connections for rt31 (default 12)
199-
--perf-duration <s> probe duration for rt30/rt31 (default 110)
214+
--perf-duration <s> probe duration for rt30/rt31 (default 180)
215+
--hup-anchor-sec <s> HUP once the probe ran this long (default 3)
216+
--hup-anchor-count <n> ... and produced this many requests (default 200)
200217
201218
Exit: 0 all pass | 100+N N cases failed | 2 usage | 3 preconditions
202219
| 4 dependency missing | 5 aborted
@@ -244,6 +261,8 @@ while [ $# -gt 0 ]; do
244261
--perf-threads) PERF_THREADS="${2:-}"; shift 2 ;;
245262
--perf-conns) PERF_CONNS="${2:-}"; shift 2 ;;
246263
--perf-duration) PERF_DURATION="${2:-}"; shift 2 ;;
264+
--hup-anchor-sec) HUP_ANCHOR_SEC="${2:-}"; shift 2 ;;
265+
--hup-anchor-count) HUP_ANCHOR_COUNT="${2:-}"; shift 2 ;;
247266
-h|--help) usage; exit 0 ;;
248267
*) die_usage "unknown option: $1" ;;
249268
esac
@@ -268,6 +287,7 @@ local -a values=()
268287
for n in TARGET_IP CLIENT CASES ROUNDS INTERVAL POLL WORKERS DRAIN_TIMEOUT STARTUP_WAIT \
269288
STREAM_MB RTE_FRESH_MIN SHUTDOWN_TIMEOUT BASELINE_DURATION GRACEFUL ZC_BUILD FAULT \
270289
FAULT_DELAY_MS PERF_THREADS PERF_CONNS PERF_DURATION \
290+
HUP_ANCHOR_SEC HUP_ANCHOR_COUNT \
271291
NGINX_BIN FSTACK_TPL PROBE_DIR OUT BUILD_MANIFEST KERNEL_NIC_IP LCORE_MASK LCORE_LIST; do
272292
values+=("$n=${!n}")
273293
done
@@ -830,6 +850,9 @@ hup_once() { # conf
830850
# as a regression either, hence the dedicated code.
831851
do_hup_case() { # tag graceful shutdown_timeout probe-kind(none|stream|lc|cps) [check-mode]
832852
local tag="$1" g="$2" st="$3" probe="$4" mode="${5:-}"
853+
# Reset the anchor: it is global so case_rt30/rt31 can quote it, and a
854+
# leftover from the previous case must never be reported as this one's.
855+
HUP_ANCHOR_LINE=""
833856
local conf out rc=0 nodata=0 wave=0 fetch=0 summary="no traffic probe"
834857
conf=$(gen_nginx_conf "$tag" "$st")
835858
push_probes || return 1
@@ -860,11 +883,19 @@ do_hup_case() { # tag graceful shutdown_timeout probe-kind(none|stream|lc|cps) [
860883
lc)
861884
launch_probe "$tag" 240 m4_lc.py --server "$TARGET_IP" --conns "$PERF_CONNS" \
862885
--interval 0.1 --duration "$PERF_DURATION" --fresh 0.5 --timeout 2 || return 1
863-
sleep 3 ;;
886+
HUP_ANCHOR_LINE=$(wait_probe_progress LC_PROGRESS "$HUP_ANCHOR_SEC" \
887+
"$HUP_ANCHOR_COUNT" 60) \
888+
|| { say "$tag: lc probe never reached the HUP anchor "
889+
"(${HUP_ANCHOR_SEC}s / ${HUP_ANCHOR_COUNT} requests)"; return 1; }
890+
say "$tag: HUP anchored at: $HUP_ANCHOR_LINE" ;;
864891
cps)
865892
launch_probe "$tag" 240 m4_cps.py --server "$TARGET_IP" --threads "$PERF_THREADS" \
866893
--duration "$PERF_DURATION" --timeout 2 || return 1
867-
sleep 3 ;;
894+
HUP_ANCHOR_LINE=$(wait_probe_progress CPS_PROGRESS "$HUP_ANCHOR_SEC" \
895+
"$HUP_ANCHOR_COUNT" 60) \
896+
|| { say "$tag: cps probe never reached the HUP anchor "
897+
"(${HUP_ANCHOR_SEC}s / ${HUP_ANCHOR_COUNT} requests)"; return 1; }
898+
say "$tag: HUP anchored at: $HUP_ANCHOR_LINE" ;;
868899
esac
869900

870901
# Only the perf cases sample process CPU: the inventory walk would
@@ -875,7 +906,10 @@ do_hup_case() { # tag graceful shutdown_timeout probe-kind(none|stream|lc|cps) [
875906
esac
876907
[ "$probe" = none ] || probe_running || { say "$tag: probe not running before the reload"; rc=1; }
877908
hup_once "$conf" || rc=1
878-
[ "$probe" = none ] || probe_running || { say "$tag: probe not running after the reload (window longer than the probe?)"; rc=1; }
909+
[ "$probe" = none ] || probe_running \
910+
|| { say "$tag: probe not running after the reload (window longer than "
911+
"the probe? drain=${HUP_DRAIN}ms duration=${PERF_DURATION}s "
912+
"anchor=${HUP_ANCHOR_LINE:-none})"; rc=1; }
879913

880914
# wait_client_summary reports 1 = no data (timeout / summary absent) and
881915
# 2 = the probe reported a summary but failed its own criterion. Only the
@@ -956,7 +990,7 @@ do_hup_case() { # tag graceful shutdown_timeout probe-kind(none|stream|lc|cps) [
956990
# (est_pps), not measured on the wire.
957991
case_rt30() {
958992
say "=== case rt30 (PT-NR-01: high CPS short connections) ==="
959-
local hrc=0 rc=0 n rate cpu_per_1k est_pps crit meas
993+
local hrc=0 rc=0 n rate cpu_per_1k est_pps crit meas anchor remaining
960994
crit="high-rate CPS: client fail=0 + reload complete + 6/6 FSM + workers back to $WORKERS"
961995
do_hup_case "rt30" 1 0 cps || hrc=$?
962996
if [ "$hrc" = "2" ]; then
@@ -968,7 +1002,13 @@ case_rt30() {
9681002
rate=$(awk -v n="${n:-0}" -v d="$PERF_DURATION" 'BEGIN { if (n == 0 || d == 0) print "NA"; else printf "%.1f", n / d }')
9691003
cpu_per_1k=$(awk -v c="$HUP_CPU_MS" -v n="${n:-0}" 'BEGIN { if (c == "NA" || n == 0) print "NA"; else printf "%.2f", c * 1000 / n }')
9701004
est_pps=$(awk -v r="$rate" 'BEGIN { if (r == "NA") print "NA"; else printf "%.0f", r * 9 }')
971-
meas="mode=cps threads=$PERF_THREADS duration=${PERF_DURATION}s n=${n:-NA} rate=${rate}/s cpu_ms=$HUP_CPU_MS cpu_per_1k=$cpu_per_1k est_pps=$est_pps drain=${HUP_DRAIN}ms fwd=${HUP_FWD} rel=${HUP_REL} $HUP_SUMMARY"
1005+
# Anchor evidence: where in the probe life the reload was sent, and how
1006+
# much of the probe was left at that moment (not after the reload).
1007+
anchor=$(printf '%s' "$HUP_ANCHOR_LINE" | sed -n 's/.*elapsed=\([0-9.]*\).*/\1/p')
1008+
[ -n "$anchor" ] || anchor="NA"
1009+
remaining=$(awk -v d="$PERF_DURATION" -v a="$anchor" \
1010+
'BEGIN { if (a == "NA") print "NA"; else printf "%.1f", d - a }')
1011+
meas="mode=cps threads=$PERF_THREADS duration=${PERF_DURATION}s n=${n:-NA} rate=${rate}/s cpu_ms=$HUP_CPU_MS cpu_per_1k=$cpu_per_1k est_pps=$est_pps drain=${HUP_DRAIN}ms fwd=${HUP_FWD} rel=${HUP_REL} anchor=${anchor}s remaining=${remaining}s $HUP_SUMMARY"
9721012
if [ "$rc" = "0" ]; then
9731013
record "rt30" "PASS" "$crit" "$meas"
9741014
else
@@ -979,7 +1019,7 @@ case_rt30() {
9791019

9801020
case_rt31() {
9811021
say "=== case rt31 (PT-NR-02: high packet rate, long connections) ==="
982-
local hrc=0 rc=0 n rate cpu_per_1k est_pps crit meas
1022+
local hrc=0 rc=0 n rate cpu_per_1k est_pps crit meas anchor remaining
9831023
crit="high packet rate: fresh_fail=0 + at most one keep-alive closure per connection (G_old drains) + reload complete + 6/6 FSM + workers back to $WORKERS"
9841024
do_hup_case "rt31" 1 0 lc perf || hrc=$?
9851025
if [ "$hrc" = "2" ]; then
@@ -991,7 +1031,13 @@ case_rt31() {
9911031
rate=$(awk -v n="${n:-0}" -v d="$PERF_DURATION" 'BEGIN { if (n == 0 || d == 0) print "NA"; else printf "%.1f", n / d }')
9921032
cpu_per_1k=$(awk -v c="$HUP_CPU_MS" -v n="${n:-0}" 'BEGIN { if (c == "NA" || n == 0) print "NA"; else printf "%.2f", c * 1000 / n }')
9931033
est_pps=$(awk -v r="$rate" 'BEGIN { if (r == "NA") print "NA"; else printf "%.0f", r * 3 }')
994-
meas="mode=lc conns=$PERF_CONNS duration=${PERF_DURATION}s reqs=${n:-NA} rate=${rate}/s cpu_ms=$HUP_CPU_MS cpu_per_1k=$cpu_per_1k est_pps=$est_pps drain=${HUP_DRAIN}ms fwd=${HUP_FWD} rel=${HUP_REL} $HUP_SUMMARY"
1034+
# Anchor evidence: where in the probe life the reload was sent, and how
1035+
# much of the probe was left at that moment (not after the reload).
1036+
anchor=$(printf '%s' "$HUP_ANCHOR_LINE" | sed -n 's/.*elapsed=\([0-9.]*\).*/\1/p')
1037+
[ -n "$anchor" ] || anchor="NA"
1038+
remaining=$(awk -v d="$PERF_DURATION" -v a="$anchor" \
1039+
'BEGIN { if (a == "NA") print "NA"; else printf "%.1f", d - a }')
1040+
meas="mode=lc conns=$PERF_CONNS duration=${PERF_DURATION}s reqs=${n:-NA} rate=${rate}/s cpu_ms=$HUP_CPU_MS cpu_per_1k=$cpu_per_1k est_pps=$est_pps drain=${HUP_DRAIN}ms fwd=${HUP_FWD} rel=${HUP_REL} anchor=${anchor}s remaining=${remaining}s $HUP_SUMMARY"
9951041
if [ "$rc" = "0" ]; then
9961042
record "rt31" "PASS" "$crit" "$meas"
9971043
else

0 commit comments

Comments
 (0)