Skip to content

Commit 92ac124

Browse files
excelle08facebook-github-bot
authored andcommitted
Adaptive driver depth in search_qps (fleet default) to fix the throughput anti-pattern
Reviewed By: YifanYuan3 Differential Revision: D112694748
1 parent 31e9bd2 commit 92ac124

3 files changed

Lines changed: 160 additions & 9 deletions

File tree

packages/feedsim/run.sh

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,11 @@ main() {
834834
# client is launched through search_qps.sh, which does not reliably forward
835835
# the parent's exported env to the driver process, so FEEDSIM_DRIVER_TLS is
836836
# also injected directly on the driver command line via `driver_bin`.
837-
driver_bin="build/workloads/ranking/DriverNodeRank"
837+
# Per-run driver knobs are injected on the DriverNodeRank command line via
838+
# `env VAR=val` (accumulated in driver_env), because search_qps.sh launches
839+
# the driver as a bare `$command &` that does not reliably inherit the
840+
# parent shell's exported env.
841+
driver_env=""
838842
if [ "${FEEDSIM_DRIVER_TLS:-0}" = "1" ]; then
839843
driver_cert_dir="${FEEDSIM_ROOT}/certs"
840844
if [ ! -r "${driver_cert_dir}/example.crt" ] || [ ! -r "${driver_cert_dir}/example.key" ]; then
@@ -844,9 +848,22 @@ main() {
844848
export FEEDSIM_TLS_CERT="${driver_cert_dir}/example.crt"
845849
export FEEDSIM_TLS_KEY="${driver_cert_dir}/example.key"
846850
export FEEDSIM_DRIVER_TLS=1
847-
driver_bin="env FEEDSIM_DRIVER_TLS=1 build/workloads/ranking/DriverNodeRank"
851+
driver_env="${driver_env} FEEDSIM_DRIVER_TLS=1"
848852
echo "Driver↔Leaf TLS: ENABLED (cert=${driver_cert_dir}/example.crt, AES-GCM)"
849853
fi
854+
# FEEDSIM_STATS_WARMUP_SECS: drop the first N seconds of latency/throughput
855+
# samples in each search_qps probe (DriverNodeRank resets its stats N secs
856+
# in) so cold-start transients don't inflate the tail and make the search
857+
# back off QPS prematurely. Default unset/0 (no warmup).
858+
if [ -n "${FEEDSIM_STATS_WARMUP_SECS:-}" ] && [ "${FEEDSIM_STATS_WARMUP_SECS}" != "0" ]; then
859+
driver_env="${driver_env} FEEDSIM_STATS_WARMUP_SECS=${FEEDSIM_STATS_WARMUP_SECS}"
860+
echo "Driver stats warmup: ${FEEDSIM_STATS_WARMUP_SECS}s (dropping cold-start samples per probe)"
861+
fi
862+
if [ -n "$driver_env" ]; then
863+
driver_bin="env${driver_env} build/workloads/ranking/DriverNodeRank"
864+
else
865+
driver_bin="build/workloads/ranking/DriverNodeRank"
866+
fi
850867
# MOCK_ZSTD_FRAC env consumed by MockServicesClient::resolveZstdFraction.
851868
# Always export so the t43 c7 default 0.75 reaches the binary.
852869
export MOCK_ZSTD_FRAC="$mock_zstd_frac"
@@ -979,16 +996,30 @@ main() {
979996
# Override via --sla-p95-ms CLI flag (handled in arg parsing above).
980997
sla_arg="95p:${sla_p95_ms}"
981998

999+
# Adaptive driver depth (fleet default): search_qps raises the driver's
1000+
# pipeline --depth in the peak phase until the server saturates (system
1001+
# CPU>=95% or p95>=SLA), giving each platform just enough offered concurrency
1002+
# to reach a real bound instead of capping on driver concurrency (the t19
1003+
# anti-pattern where big boxes sat at ~80% CPU with p95 far below SLA).
1004+
# Enabled by default up to depth 8; set FEEDSIM_ADAPTIVE_DEPTH_MAX=0 to
1005+
# disable and fall back to the fixed FEEDSIM_DRIVER_DEPTH (default 1).
1006+
sqps_adaptive_arg=""
1007+
adaptive_depth_max="${FEEDSIM_ADAPTIVE_DEPTH_MAX:-8}"
1008+
if [ "$adaptive_depth_max" != "0" ]; then
1009+
sqps_adaptive_arg="-D ${adaptive_depth_max}"
1010+
fi
1011+
9821012
if [ -z "$fixed_qps" ] && [ "$auto_driver_threads" != "1" ]; then
9831013
benchreps_tell_state "before search_qps"
9841014
echo "search_qps SLA: ${sla_arg}"
9851015
# shellcheck disable=SC2086
986-
scripts/search_qps.sh -w 15 -f 300 -s "$sla_arg" -P "$LEAF_PID" -B "$BREAKDOWN_FOLDER" $qps_threshold_args $no_retry_args -o "${FEEDSIM_ROOT}/${result_filename}" -- \
1016+
scripts/search_qps.sh -t "${FEEDSIM_EXPERIMENT_TIME:-120}" -w 15 -f 300 -s "$sla_arg" $sqps_adaptive_arg -P "$LEAF_PID" -B "$BREAKDOWN_FOLDER" $qps_threshold_args $no_retry_args -o "${FEEDSIM_ROOT}/${result_filename}" -- \
9871017
$driver_bin \
9881018
--server "0.0.0.0:$port" \
9891019
--monitor_port "$client_monitor_port" \
9901020
--threads="${driver_threads}" \
9911021
--connections=4 \
1022+
--depth="${FEEDSIM_DRIVER_DEPTH:-1}" \
9921023
$client_feature_opts \
9931024
$silesia_opts \
9941025
$req_size_opts
@@ -997,10 +1028,11 @@ main() {
9971028
benchreps_tell_state "before search_qps"
9981029
echo "search_qps SLA: ${sla_arg}"
9991030
# shellcheck disable=SC2086
1000-
scripts/search_qps.sh -a -w 15 -f 300 -s "$sla_arg" -P "$LEAF_PID" -B "$BREAKDOWN_FOLDER" $qps_threshold_args $no_retry_args -o "${FEEDSIM_ROOT}/${result_filename}" -- \
1031+
scripts/search_qps.sh -a -t "${FEEDSIM_EXPERIMENT_TIME:-120}" -w 15 -f 300 -s "$sla_arg" $sqps_adaptive_arg -P "$LEAF_PID" -B "$BREAKDOWN_FOLDER" $qps_threshold_args $no_retry_args -o "${FEEDSIM_ROOT}/${result_filename}" -- \
10011032
$driver_bin \
10021033
--monitor_port "$client_monitor_port" \
10031034
--server "0.0.0.0:$port" \
1035+
--depth="${FEEDSIM_DRIVER_DEPTH:-1}" \
10041036
$client_feature_opts \
10051037
$silesia_opts \
10061038
$req_size_opts
@@ -1029,6 +1061,7 @@ main() {
10291061
--monitor_port "$client_monitor_port" \
10301062
--threads="${num_workers}" \
10311063
--connections="${num_connections}" \
1064+
--depth="${FEEDSIM_DRIVER_DEPTH:-1}" \
10321065
$client_feature_opts \
10331066
$silesia_opts \
10341067
$req_size_opts

packages/feedsim/third_party/src/scripts/search_qps.sh

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ echo "${SCRIPT_NAME}: DCPERF_PERF_RECORD=${DCPERF_PERF_RECORD}"
3131
function benchreps_tell_state () {
3232
date +"%Y-%m-%d_%T ${1}" >> $BREPS_LFILE
3333
}
34+
35+
# ─── CPU utilization helpers (used by adaptive depth) ────────────────────────
36+
# Read /proc/stat's aggregate cpu line and echo "total idle_all" jiffies.
37+
cpu_snapshot() {
38+
local cpu u n s idle iow irq sirq st rest
39+
read -r cpu u n s idle iow irq sirq st rest < /proc/stat
40+
local idle_all=$((idle + iow))
41+
local total=$((u + n + s + idle + iow + irq + sirq + st))
42+
echo "$total $idle_all"
43+
}
44+
# System-wide CPU busy% (100 - idle%) measured over the next $1 seconds.
45+
cpu_busy_over() {
46+
local secs="$1" s1 s2 t1 i1 t2 i2 dt di
47+
s1=$(cpu_snapshot); t1=${s1% *}; i1=${s1#* }
48+
sleep "$secs"
49+
s2=$(cpu_snapshot); t2=${s2% *}; i2=${s2#* }
50+
dt=$((t2 - t1)); di=$((i2 - i1))
51+
if [ "$dt" -le 0 ]; then echo "0"; return; fi
52+
echo "scale=1; (($dt - $di) * 100) / $dt" | bc
53+
}
3454
# Source runtime breakdown utilities if they exist
3555
if [ -f "${BENCHPRESS_ROOT}/packages/common/runtime_breakdown_utils.sh" ]; then
3656
source "${BENCHPRESS_ROOT}/packages/common/runtime_breakdown_utils.sh"
@@ -99,6 +119,11 @@ mutilate (EuroSys \'14) [https://github.com/leverich/mutilate]
99119
without retrying. Optional
100120
-P PID of the process to log runtime breakdowns. Optional
101121
-B Folder to log runtime breakdowns. Optional
122+
-D Adaptive depth: max driver pipeline depth. When set, the peak
123+
phase raises the driver's --depth from 1 until the server is
124+
saturated (system CPU >= 95% OR achieved p95 >= SLA), then holds
125+
that depth for the QPS search. Overrides any --depth in the
126+
driver command. Optional.
102127
EOF
103128
}
104129

@@ -145,7 +170,7 @@ run_loadtest() {
145170
for r in $(seq 1 $load_test_retries); do
146171
# run the command, saving result to tmpfile
147172
local tmp_file=$(mktemp)
148-
$command $threads_arg $qps_arg &>$tmp_file &
173+
$command $threads_arg $qps_arg $adaptive_depth_arg &>$tmp_file &
149174
LOADTEST_PID=$!
150175

151176
if [ "$no_retry_mode" = "1" ]; then
@@ -301,14 +326,19 @@ max_warmup_iterations=10
301326
no_retry_mode=""
302327
breakdown_pid=""
303328
breakdown_folder=""
329+
adaptive_depth_max="" # -D: when set, search_qps raises driver --depth in the
330+
adaptive_depth_arg="" # peak phase until the server saturates (CPU>=95% or p95>=SLA)
304331

305332
OPTIND=1 # Reset is necessary if getopts was used previously in the script. It is a good idea to make this local in a function.
306-
while getopts "ht:f:w:m:s:q:ao:r:x:NP:B:" opt; do
333+
while getopts "ht:f:w:m:s:q:ao:r:x:NP:B:D:" opt; do
307334
case "$opt" in
308335
h)
309336
show_help
310337
exit 0
311338
;;
339+
D)
340+
adaptive_depth_max=$OPTARG
341+
;;
312342
t)
313343
experiment_time=$OPTARG
314344
;;
@@ -364,6 +394,14 @@ fi
364394
# remaining argument is loadtest command
365395
command=$@
366396

397+
# In adaptive-depth mode, search_qps owns the driver's --depth: strip any fixed
398+
# --depth from the command so our per-attempt --depth is the only one, and start
399+
# the peak search at depth=1.
400+
if [ -n "$adaptive_depth_max" ]; then
401+
command=$(echo "$command" | sed -E 's/[[:space:]]*--depth=[0-9]+//g')
402+
adaptive_depth_arg="--depth=1"
403+
fi
404+
367405
# make sure latency_type and latency_target are specified
368406
if [[ -z "$fixed_qps" ]] && ( [[ $latency_type = "" ]] || [[ $latency_target = "" ]] ); then
369407
echo 'error: -s metric:target must be specified' >&2; exit 1
@@ -513,7 +551,40 @@ fi
513551

514552
# find peak QPS
515553
benchreps_tell_state "before peak_qps"
516-
run_loadtest peak_qps measured_latency "" ""
554+
if [ -n "$adaptive_depth_max" ]; then
555+
# Adaptive depth: the peak load test offers at most threads*connections*depth
556+
# concurrent requests. Starting at depth=1, keep raising depth (and re-running
557+
# peak) until the server saturates — system CPU >= 95% OR achieved p95 >= SLA
558+
# — so platforms that need more offered concurrency reach a real bound instead
559+
# of capping on driver concurrency (the t19 anti-pattern). The selected depth
560+
# is then held for the QPS search / tuning / final phases.
561+
cur_depth=1
562+
while : ; do
563+
adaptive_depth_arg="--depth=$cur_depth"
564+
# Sample system CPU busy% over a mid-run window while the peak load runs.
565+
cpu_busy_file="/tmp/adaptive_cpu_busy_$$"
566+
( sleep 20; cpu_busy_over 40 > "$cpu_busy_file" ) &
567+
cpu_sampler_pid=$!
568+
run_loadtest peak_qps measured_latency "" ""
569+
wait "$cpu_sampler_pid" 2>/dev/null
570+
cpu_busy=$(cat "$cpu_busy_file" 2>/dev/null || echo 0)
571+
rm -f "$cpu_busy_file"
572+
cpu_sat=$(echo "${cpu_busy:-0} >= 95" | bc 2>/dev/null || echo 0)
573+
lat_sat=$(echo "$measured_latency >= $latency_target" | bc 2>/dev/null || echo 0)
574+
printf "adaptive-depth: depth=%d peak_qps=%.2f p95=%.2f cpu_busy=%s%% cpu_sat=%s lat_sat=%s\n" \
575+
"$cur_depth" "$peak_qps" "$measured_latency" "${cpu_busy:-0}" "$cpu_sat" "$lat_sat"
576+
echo "adaptive-depth: depth=$cur_depth peak_qps=$peak_qps p95=$measured_latency cpu_busy=${cpu_busy}% cpu_sat=$cpu_sat lat_sat=$lat_sat" >> $BREPS_LFILE
577+
if [ "$cpu_sat" -eq 1 ] || [ "$lat_sat" -eq 1 ] || [ "$cur_depth" -ge "$adaptive_depth_max" ]; then
578+
break
579+
fi
580+
cur_depth=$((cur_depth + 1))
581+
sleep "$wait_time"
582+
done
583+
echo "adaptive-depth: SELECTED depth=$cur_depth (cpu_busy=${cpu_busy}%, p95=$measured_latency, sla=$latency_target)" >> $BREPS_LFILE
584+
printf "adaptive-depth: selected depth=%d (cpu_busy=%s%%, p95=%.2f)\n" "$cur_depth" "${cpu_busy:-0}" "$measured_latency"
585+
else
586+
run_loadtest peak_qps measured_latency "" ""
587+
fi
517588
printf "peak qps = %.2f, latency = %.2f\n" $peak_qps $measured_latency
518589
benchreps_tell_state "after peak_qps"
519590

packages/feedsim/third_party/src/workloads/ranking/FeedSimDriver.cc

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,17 @@ void FeedSimDriver::enableMonitoring(uint16_t port) {
864864
impl_->monitor_port = port;
865865
}
866866

867+
namespace {
868+
// One-shot timer callback used by FEEDSIM_STATS_WARMUP_SECS: drops all
869+
// latency/throughput samples collected so far so the final stats reflect only
870+
// the post-warmup (steady-state) window. Runs on the DriverThread's own event
871+
// base, so it never races that thread's logRequest()/logResponse() writes to
872+
// the same DriverStats.
873+
void resetDriverStatsCb(evutil_socket_t, short, void* arg) {
874+
reinterpret_cast<DriverStats*>(arg)->reset();
875+
}
876+
} // namespace
877+
867878
void FeedSimDriver::run(uint32_t num_threads, bool thread_pinning,
868879
uint32_t num_connections_per_thread,
869880
uint32_t max_connection_depth) {
@@ -876,6 +887,23 @@ void FeedSimDriver::run(uint32_t num_threads, bool thread_pinning,
876887
impl_->running = true;
877888
impl_->total_stats = std::make_unique<DriverStats>(1000);
878889

890+
// FEEDSIM_STATS_WARMUP_SECS: when >0, each DriverThread drops the samples it
891+
// collected in the first N seconds of the measurement window (a per-thread
892+
// one-shot timer calls DriverStats::reset()). This excludes cold-start
893+
// transients (connection ramp, cache/JIT warmup, first-touch faults) from
894+
// the reported latency distribution, giving search_qps a more stable p95 so
895+
// it doesn't back off QPS on a noisy tail (the t19 anti-pattern).
896+
int stats_warmup_secs = 0;
897+
{
898+
const char* e = std::getenv("FEEDSIM_STATS_WARMUP_SECS");
899+
if (e != nullptr && e[0] != '\0') {
900+
stats_warmup_secs = std::atoi(e);
901+
if (stats_warmup_secs < 0) {
902+
stats_warmup_secs = 0;
903+
}
904+
}
905+
}
906+
879907
// Barrier for thread init synchronization
880908
pthread_barrier_t init_barrier;
881909
pthread_barrier_init(&init_barrier, nullptr, num_threads + 1);
@@ -921,7 +949,7 @@ void FeedSimDriver::run(uint32_t num_threads, bool thread_pinning,
921949

922950
// Start thread
923951
dt->thread = std::thread([this, &dt_ref = *dt, &init_barrier,
924-
thread_pinning, i]() {
952+
thread_pinning, i, stats_warmup_secs]() {
925953
// CPU affinity
926954
if (thread_pinning) {
927955
cpu_set_t mask;
@@ -953,6 +981,18 @@ void FeedSimDriver::run(uint32_t num_threads, bool thread_pinning,
953981
// Start making requests
954982
TestDriver::Impl::makeRequests(*dt_ref.driver);
955983

984+
// Schedule the cold-start stats reset on this thread's own base (added
985+
// here, before dispatch, so it fires from this thread — no data race with
986+
// the stats writes). Fires once ~stats_warmup_secs into steady traffic.
987+
if (stats_warmup_secs > 0) {
988+
struct timeval warmup_tv {
989+
stats_warmup_secs, 0
990+
};
991+
event_base_once(
992+
dt_ref.base, -1, EV_TIMEOUT, resetDriverStatsCb,
993+
&dt_ref.driver->impl_->current_stats, &warmup_tv);
994+
}
995+
956996
// Run event loop
957997
event_base_dispatch(dt_ref.base);
958998
});
@@ -1070,7 +1110,14 @@ void FeedSimDriver::run(uint32_t num_threads, bool thread_pinning,
10701110
}
10711111

10721112
double end_time = getTimeSec();
1073-
double elapsed = end_time - start_time;
1113+
// With a stats warmup, samples were reset ~stats_warmup_secs into the run, so
1114+
// the throughput denominator must be the post-warmup window (else QPS would
1115+
// be understated by counting post-warmup queries over the full duration).
1116+
double meas_start = start_time;
1117+
if (stats_warmup_secs > 0) {
1118+
meas_start = start_time + stats_warmup_secs;
1119+
}
1120+
double elapsed = end_time - meas_start;
10741121

10751122
// Aggregate stats from all threads
10761123
for (auto& dt : impl_->threads) {

0 commit comments

Comments
 (0)