2323#include < algorithm>
2424#include < atomic>
2525#include < cassert>
26+ #include < chrono>
27+ #include < cstdlib>
2628#include < cmath>
2729#include < deque>
30+ #include < fstream>
31+ #include < iomanip>
2832#include < iostream>
2933#include < limits>
34+ #include < sstream>
3035#include < map>
3136#include < memory>
3237#include < mutex>
@@ -129,6 +134,7 @@ void DriverStats::logResponse(uint32_t /*type*/, uint64_t latency_ns,
129134 uint32_t packet_size) {
130135 sampler_->sample (static_cast <double >(latency_ns));
131136 rx_bytes_ += packet_size;
137+ completed_count_++;
132138}
133139
134140void DriverStats::logFirstStoryLatency (uint64_t latency_ns) {
@@ -145,6 +151,7 @@ void DriverStats::accumulate(const DriverStats& other) {
145151 tx_bytes_ += other.tx_bytes_ ;
146152 rx_bytes_ += other.rx_bytes_ ;
147153 query_count_ += other.query_count_ ;
154+ completed_count_ += other.completed_count_ ;
148155 session_count_ += other.session_count_ ;
149156}
150157
@@ -154,6 +161,7 @@ void DriverStats::reset() {
154161 tx_bytes_ = 0 ;
155162 rx_bytes_ = 0 ;
156163 query_count_ = 0 ;
164+ completed_count_ = 0 ;
157165 session_count_ = 0 ;
158166 start_time_ = getTimeNano ();
159167}
@@ -440,8 +448,42 @@ void TestDriver::Impl::eventCb(struct bufferevent* /*bev*/, int16_t events,
440448 }
441449}
442450
451+ // DCPERF_DRIVER_INFLIGHT_CAP: per-thread soft cap on (sent - completed).
452+ // 0 means disabled (back-compat). When set, nextRequestCb skips firing
453+ // until in-flight drops back below the cap — couples driver firing rate
454+ // to actual server completion rate so requested_qps > server_qps cannot
455+ // pin in-flight at the connection-layer hard cap and drive latency up
456+ // indefinitely. Read once at startup (cached in g_driver_inflight_cap)
457+ // so per-tick checks don't pay env-lookup cost.
458+ static uint64_t g_driver_inflight_cap = []() {
459+ const char * env = std::getenv (" DCPERF_DRIVER_INFLIGHT_CAP" );
460+ if (env == nullptr || env[0 ] == ' \0 ' ) return uint64_t {0 };
461+ char * end = nullptr ;
462+ unsigned long v = std::strtoul (env, &end, 10 );
463+ return static_cast <uint64_t >(v);
464+ }();
465+
443466void TestDriver::Impl::nextRequestCb (evutil_socket_t , int16_t , void * arg) {
444467 auto * driver = reinterpret_cast <TestDriver*>(arg);
468+ auto & impl = *driver->impl_ ;
469+ if (g_driver_inflight_cap > 0 ) {
470+ uint64_t sent = impl.current_stats .getSentCount ();
471+ uint64_t done = impl.current_stats .getCompletedCount ();
472+ uint64_t in_flight = sent > done ? (sent - done) : 0 ;
473+ if (in_flight >= g_driver_inflight_cap) {
474+ // Server isn't keeping up. Skip this firing and re-arm the timer
475+ // for the same delay so the next pacing tick is still on schedule
476+ // (we're not trying to "make up" the missed call — that's the
477+ // whole point of the back-off).
478+ if (impl.next_request_delay_us != 0 && impl.next_request_event != nullptr ) {
479+ struct timeval tv;
480+ tv.tv_sec = impl.next_request_delay_us / 1000000 ;
481+ tv.tv_usec = impl.next_request_delay_us % 1000000 ;
482+ evtimer_add (impl.next_request_event , &tv);
483+ }
484+ return ;
485+ }
486+ }
445487 makeRequests (*driver);
446488}
447489
@@ -452,6 +494,15 @@ void TestDriver::Impl::makeRequests(TestDriver& driver) {
452494 impl.num_backlogged_requests ++;
453495 return ;
454496 }
497+ if (g_driver_inflight_cap > 0 ) {
498+ uint64_t sent = impl.current_stats .getSentCount ();
499+ uint64_t done = impl.current_stats .getCompletedCount ();
500+ if (sent > done && (sent - done) >= g_driver_inflight_cap) {
501+ // Same back-off rule applies inside the spin loop (which only
502+ // spins when next_request_delay_us == 0, i.e. unpaced runs).
503+ return ;
504+ }
505+ }
455506 impl.make_request_cb (impl.thread_id , driver);
456507 } while (impl.next_request_delay_us == 0 );
457508}
@@ -679,6 +730,11 @@ struct FeedSimDriver::Impl {
679730
680731 event_base* main_base = nullptr ;
681732 std::atomic<bool > running{false };
733+
734+ // DCPERF_DRIVER_QPS_TRACE: per-second telemetry thread. Idle unless
735+ // the env var is set to a non-empty, non-"0" value at run() time.
736+ std::thread trace_thread;
737+ std::atomic<bool > trace_running{false };
682738};
683739
684740// ─── FeedSimDriver public methods ───────────────────────────────────────────
@@ -834,6 +890,91 @@ void FeedSimDriver::run(uint32_t num_threads, bool thread_pinning,
834890 pthread_barrier_wait (&init_barrier);
835891 pthread_barrier_destroy (&init_barrier);
836892
893+ // DCPERF_DRIVER_QPS_TRACE: spawn a 1Hz trace thread when the env var
894+ // is set (any non-empty, non-"0" value enables it). Logs to the path
895+ // in DCPERF_DRIVER_QPS_TRACE_FILE, defaulting to
896+ // /tmp/driver_qps_trace_<pid>.log. The default uses pid so that
897+ // multi-instance feedsim runs (each DriverNodeRank is a separate
898+ // process) produce per-instance trace files instead of stomping on
899+ // one shared file. Each line:
900+ // t=<elapsed_s> total{sent_qps=X done_qps=Y inflight=Z} |
901+ // t0{sent=W done=V inflight=U} t1{...} ...
902+ // Aligned uint64 reads of query_count_/completed_count_ are atomic on
903+ // x86-64 and aarch64; no extra synchronization needed for a 1Hz poll.
904+ //
905+ // CRITICAL: do NOT print a startup banner to stderr — search_qps.sh
906+ // reads DriverNodeRank's stdout AND stderr (combined via 2>&1), and a
907+ // stderr write here interleaves with the (fully-buffered) stdout
908+ // "final requested_qps = ..., measured_qps = ..., latency = ..."
909+ // line on the pipe, fragmenting it so the parser captures wrong
910+ // values (regression observed in the t7 sweep). The trace file's
911+ // existence is sufficient evidence the trace was enabled.
912+ {
913+ const char * env = std::getenv (" DCPERF_DRIVER_QPS_TRACE" );
914+ if (env != nullptr && env[0 ] != ' \0 ' && std::string (env) != " 0" ) {
915+ const char * path_env = std::getenv (" DCPERF_DRIVER_QPS_TRACE_FILE" );
916+ std::string trace_path;
917+ if (path_env != nullptr && path_env[0 ] != ' \0 ' ) {
918+ trace_path = std::string (path_env);
919+ } else {
920+ std::ostringstream p;
921+ p << " /tmp/driver_qps_trace_" << ::getpid () << " .log" ;
922+ trace_path = p.str ();
923+ }
924+ impl_->trace_running = true ;
925+ impl_->trace_thread = std::thread (
926+ [impl_ptr = impl_.get (), trace_path]() {
927+ std::ofstream out (trace_path, std::ios::out | std::ios::app);
928+ if (!out) return ; // silent: any cerr write would corrupt search_qps
929+ const auto t0 = std::chrono::steady_clock::now ();
930+ std::vector<uint64_t > last_sent (impl_ptr->threads .size (), 0 );
931+ std::vector<uint64_t > last_done (impl_ptr->threads .size (), 0 );
932+ for (size_t i = 0 ; i < impl_ptr->threads .size (); i++) {
933+ const auto & s = impl_ptr->threads [i]->driver ->impl_ ->current_stats ;
934+ last_sent[i] = s.getSentCount ();
935+ last_done[i] = s.getCompletedCount ();
936+ }
937+ out << " # DCPERF_DRIVER_QPS_TRACE start, threads="
938+ << impl_ptr->threads .size () << std::endl;
939+ while (impl_ptr->trace_running .load (std::memory_order_acquire)) {
940+ std::this_thread::sleep_for (std::chrono::seconds (1 ));
941+ if (!impl_ptr->trace_running .load (std::memory_order_acquire)) {
942+ break ;
943+ }
944+ auto now = std::chrono::steady_clock::now ();
945+ double elapsed_s = std::chrono::duration<double >(now - t0).count ();
946+ uint64_t total_sent_qps = 0 ;
947+ uint64_t total_done_qps = 0 ;
948+ uint64_t total_in_flight = 0 ;
949+ std::ostringstream per_thread;
950+ for (size_t i = 0 ; i < impl_ptr->threads .size (); i++) {
951+ const auto & s =
952+ impl_ptr->threads [i]->driver ->impl_ ->current_stats ;
953+ uint64_t sent = s.getSentCount ();
954+ uint64_t done = s.getCompletedCount ();
955+ uint64_t dsent = sent - last_sent[i];
956+ uint64_t ddone = done - last_done[i];
957+ uint64_t in_flight = sent > done ? (sent - done) : 0 ;
958+ last_sent[i] = sent;
959+ last_done[i] = done;
960+ total_sent_qps += dsent;
961+ total_done_qps += ddone;
962+ total_in_flight += in_flight;
963+ per_thread << " t" << i << " {sent=" << dsent
964+ << " done=" << ddone
965+ << " inflight=" << in_flight << " }" ;
966+ }
967+ out << " t=" << std::fixed << std::setprecision (1 ) << elapsed_s
968+ << " total{sent_qps=" << total_sent_qps
969+ << " done_qps=" << total_done_qps
970+ << " inflight=" << total_in_flight << " } |"
971+ << per_thread.str () << std::endl;
972+ }
973+ out << " # DCPERF_DRIVER_QPS_TRACE stop" << std::endl;
974+ });
975+ }
976+ }
977+
837978 double start_time = getTimeSec ();
838979
839980 // Main event loop (for SIGINT handling)
@@ -920,6 +1061,16 @@ void FeedSimDriver::shutdown() {
9201061 if (impl_->main_base ) {
9211062 event_base_loopbreak (impl_->main_base );
9221063 }
1064+
1065+ // Step 5: stop the DCPERF_DRIVER_QPS_TRACE thread (if it was started).
1066+ // Do this after the main loop has been broken so the trace thread
1067+ // doesn't keep firing through the join(); the trace lambda checks
1068+ // trace_running every iteration and exits cleanly.
1069+ if (impl_->trace_running .exchange (false )) {
1070+ if (impl_->trace_thread .joinable ()) {
1071+ impl_->trace_thread .join ();
1072+ }
1073+ }
9231074}
9241075
9251076} // namespace feedsim
0 commit comments