Skip to content

Commit 49e3370

Browse files
excelle08meta-codesync[bot]
authored andcommitted
Add SemiFuture driver API + RunSession + first-story latency (#724)
Summary: Pull Request resolved: #724 Programmer-A scope of Phase 6 per `~/feedsim_v2/docs/phase6_researcher_notes.md` sections 1, 2, 3, and 6. Server-side handlers (§4) and legacy-code deletion (§5) are split into Phase 6/2 (Programmer-B) and Phase 6/3 (Programmer-C) commits. Four changes: 1. `FeedSimDriver` SemiFuture API (`FeedSimDriver.h`, `FeedSimDriver.cc`). New `TestDriver::sendRequestAndAwait(type, payload, length)` returns a `folly::SemiFuture<std::string>` that fulfills with the response payload bytes when the matching `ResponsePacketHeader` arrives. Implementation: per-`TestDriver` `folly::F14FastMap<uint64_t, folly::Promise<std::string>> pending_promises` keyed by `request_id`; `next_request_id` promoted from a plain `uint64_t` to `std::atomic<uint64_t>` so callers from arbitrary threads can mint IDs without contention. `event_base_once` hops the actual write onto the libevent thread (the only thread that may touch the bufferevent state). `readCb` parses the response header, copies the payload out of the libevent buffer, looks up the `request_id` under `pending_mutex`, moves the promise out, drops the lock, and calls `setValue`. The legacy fire-and-forget `sendRequest` stays in place — Phase 6-C deletes it. 2. `RunSession` orchestration (`DriverNodeRank.cc`). New `RunSession(thread_id, driver, thread_data)` builds the 4-step session pipeline that mirrors prod multifeed_aggregator: createAndPrimeSession (await) -> getStoriesUncompressed (HOLD future) + streamData * N (parallel) + optional streamIfrPriorityRanking coin flip -> await all streamData -> await getStoriesUncompressed and record first-story latency -> getAllStories (await) -> done. Encoders `encodeCreateAndPrime`, `encodeGetStories`, `encodeStreamData`, `encodeStreamIfrPriority`, `encodeGetAllStories` populate the typed thrift structs from Phase 4-B with realistic field values, sample target wire sizes from `RpcDistRegistry::inboundRequestSize()`, and pad the dominant binary field with Silesia bytes (compression-realistic). `query_id = (thread_id << 32) | session_counter++` so the leaf's `query_id >> 32` shard derivation lands all four-six RPCs for one session on the same RANKER worker. The new `StartSessionLoop` callback dispatches `RunSession` on a dedicated `CPUThreadPoolExecutor("DriverSession", num_threads)` and chains the pacing-timer rearm (`TestDriver::scheduleNextSession`) onto the SemiFuture completion. Session mode is gated by `--rpc_dist_json` — without it, the legacy `MakeRequest` path remains for backward compat during the migration. 3. First-story latency histogram (`FeedSimDriver.h`, `FeedSimDriver.cc`). Second `LatencySampler` added to `DriverStats` (`first_story_sampler_`). `recordFirstStoryLatencyNs(uint64_t)` on `TestDriver` forwards into it; `recordSessionComplete()` increments a session counter. `printStats()` emits a new "Stats for first-story latency" block with `fs_count`, `fs_sessions`, and `fs_min/avg/50p/90p/95p/99p/99.9p` lines. The `fs_*` prefix keeps `search_qps.sh`'s per-percentile greps unambiguous. 4. `search_qps.sh` parsing (`packages/feedsim/third_party/src/scripts/search_qps.sh`). Per-response latency greps are anchored on ` ` (leading whitespace) so they don't accidentally match the new first-story block. Four new CSV columns appended (`fs_50p_ms, fs_90p_ms, fs_95p_ms, fs_99p_ms`). New `fs_<percentile>` latency-type targets supported alongside the existing `<percentile>` ones (use `-s fs_95p:500` to search against the prod-equivalent first-story SLA per researcher §6). Driver-side `RpcDistRegistry` inbound exposure (`RpcDistRegistry.h`) — added `InboundIdx` enum (5 methods), `inboundMethodNames()`, `inboundRequestSize/Response/LatencyUs(InboundIdx)` accessors, parallel `inbound_req_/resp_/lat_` arrays, and updated `load()` to populate them from the `inbound` JSON section. Programmer-B independently introduced a near-identical enum with slightly different naming (`kCreateAndPrimeSession` vs `CREATE_AND_PRIME`); kept B's naming and consumed it from `DriverNodeRank.cc` so Programmer-C can keep a single canonical version when deduplicating. CLI flags added (`DriverNodeRankCmdline.ggo`): `--rpc_dist_json` (path, gates session mode), `--streamdata_per_session` (int, default=2; 0 randomizes uniform[1,3]), `--stream_ifr_probability` (float, default=0.045 to match prod ratio per researcher §2). Reviewed By: charles-typ Differential Revision: D103795176
1 parent 1b0890e commit 49e3370

6 files changed

Lines changed: 1029 additions & 41 deletions

File tree

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,18 @@ run_loadtest() {
201201
exit 1;
202202
fi
203203

204-
if grep -q "$latency_type: [0-9]\+.\([0-9]\+\)\? ms" $tmp_file; then
205-
local latency=$(cat $tmp_file | grep $latency_type | awk '{print $2}')
204+
# Phase 6: support `fs_<percentile>` (first-story) targets by greppng
205+
# the prefixed line. Anchor non-fs targets on a leading space so the
206+
# `fs_*` block doesn't accidentally satisfy a `95p:` query when the
207+
# caller actually wanted the per-response block.
208+
local latency_grep_pattern="$latency_type: [0-9]\+.\([0-9]\+\)\? ms"
209+
if [[ "$latency_type" == fs_* ]]; then
210+
: # already prefixed
211+
else
212+
latency_grep_pattern=" $latency_type: [0-9]\+.\([0-9]\+\)\? ms"
213+
fi
214+
if grep -q "$latency_grep_pattern" $tmp_file; then
215+
local latency=$(cat $tmp_file | grep -- "$latency_grep_pattern" | head -n1 | awk '{print $2}')
206216
else
207217
echo "Could not find latency in loadtest output" >&2
208218
echo "Contents of loadtest output:" >&2
@@ -231,18 +241,33 @@ run_loadtest() {
231241

232242
local total_queries=$(cat $tmp_file | awk '/QPS/ {print substr($4,2);}')
233243

234-
local min_ms=$(cat $tmp_file | awk '/min:/ {print $2;}')
235-
local avg_ms=$(cat $tmp_file | awk '/avg:/ {print $2;}')
236-
local p50_ms=$(cat $tmp_file | awk '/50p:/ {print $2;}')
237-
local p90_ms=$(cat $tmp_file | awk '/90p:/ {print $2;}')
238-
local p95_ms=$(cat $tmp_file | awk '/95p:/ {print $2;}')
239-
local p99_ms=$(cat $tmp_file | awk '/99p:/ {print $2;}')
240-
local p99_9_ms=$(cat $tmp_file | awk '/99\.9p:/ {print $2;}')
244+
# Per-response latency block. Anchor on a leading space so the
245+
# first-story `fs_*` block (which uses `fs_50p:` etc.) doesn't
246+
# bleed into these matches.
247+
local min_ms=$(cat $tmp_file | awk '/ min:/ {print $2;}')
248+
local avg_ms=$(cat $tmp_file | awk '/ avg:/ {print $2;}')
249+
local p50_ms=$(cat $tmp_file | awk '/ 50p:/ {print $2;}')
250+
local p90_ms=$(cat $tmp_file | awk '/ 90p:/ {print $2;}')
251+
local p95_ms=$(cat $tmp_file | awk '/ 95p:/ {print $2;}')
252+
local p99_ms=$(cat $tmp_file | awk '/ 99p:/ {print $2;}')
253+
local p99_9_ms=$(cat $tmp_file | awk '/ 99\.9p:/ {print $2;}')
254+
255+
# Phase 6 first-story latency block. Always present in driver output
256+
# (zeros if no samples), so unconditional capture is safe.
257+
local fs_p50_ms=$(cat $tmp_file | awk '/fs_50p:/ {print $2;}')
258+
local fs_p90_ms=$(cat $tmp_file | awk '/fs_90p:/ {print $2;}')
259+
local fs_p95_ms=$(cat $tmp_file | awk '/fs_95p:/ {print $2;}')
260+
local fs_p99_ms=$(cat $tmp_file | awk '/fs_99p:/ {print $2;}')
261+
fs_p50_ms=${fs_p50_ms:-0.000}
262+
fs_p90_ms=${fs_p90_ms:-0.000}
263+
fs_p95_ms=${fs_p95_ms:-0.000}
264+
fs_p99_ms=${fs_p99_ms:-0.000}
241265

242266
printf '%d,%d,%.2f,%.2f,' "$experiment_time" "$total_queries" "$3" "$qps" >> $output_csv_file
243267
printf '%d,%d,%.2f,%.2f,' "$total_bytes_rx" "$total_bytes_tx" "$rx_mbps" "$tx_mbps" >> $output_csv_file
244268
printf '%.3f,%.3f,%.3f,%.3f,' "$min_ms" "$avg_ms" "$p50_ms" "$p90_ms" >> $output_csv_file
245-
printf '%.3f,%.3f,%.3f\n' "$p95_ms" "$p99_ms" "$p99_9_ms" >> $output_csv_file
269+
printf '%.3f,%.3f,%.3f,' "$p95_ms" "$p99_ms" "$p99_9_ms" >> $output_csv_file
270+
printf '%.3f,%.3f,%.3f,%.3f\n' "$fs_p50_ms" "$fs_p90_ms" "$fs_p95_ms" "$fs_p99_ms" >> $output_csv_file
246271

247272
fi
248273

@@ -344,11 +369,16 @@ if [[ -z "$fixed_qps" ]] && ( [[ $latency_type = "" ]] || [[ $latency_target = "
344369
echo 'error: -s metric:target must be specified' >&2; exit 1
345370
fi
346371

347-
# make sure latency_type is a recognized type
372+
# make sure latency_type is a recognized type. Phase 6 adds `fs_*`
373+
# (first-story) variants so search_qps can search against the new
374+
# session-mode SLA.
348375
if [[ $latency_type != "avg" ]] && [[ $latency_type != "50p" ]] && \
349376
[[ $latency_type != "90p" ]] && [[ $latency_type != "95p" ]] && \
350-
[[ $latency_type != "99p" ]] && [[ $latency_type != "99.9p" ]]; then
351-
echo 'error: metric must be avg|50p|90p|95p|99p|99.9p' >&2; exit 1
377+
[[ $latency_type != "99p" ]] && [[ $latency_type != "99.9p" ]] && \
378+
[[ $latency_type != "fs_avg" ]] && [[ $latency_type != "fs_50p" ]] && \
379+
[[ $latency_type != "fs_90p" ]] && [[ $latency_type != "fs_95p" ]] && \
380+
[[ $latency_type != "fs_99p" ]] && [[ $latency_type != "fs_99.9p" ]]; then
381+
echo 'error: metric must be (avg|50p|90p|95p|99p|99.9p) or fs_(avg|50p|90p|95p|99p|99.9p)' >&2; exit 1
352382
fi
353383

354384
# check to make sure experiment_time is an integer
@@ -380,7 +410,11 @@ avg_ms,\
380410
90p_ms,\
381411
95p_ms,\
382412
99p_ms,\
383-
99.9p_ms"
413+
99.9p_ms,\
414+
fs_50p_ms,\
415+
fs_90p_ms,\
416+
fs_95p_ms,\
417+
fs_99p_ms"
384418

385419
echo $header > $output_csv_file
386420
fi

0 commit comments

Comments
 (0)