Skip to content

Commit 6ab9029

Browse files
charles-typmeta-codesync[bot]
authored andcommitted
Add periodic server-side stats reporting (#484)
Summary: Pull Request resolved: #484 During long-running ucachebench warmup and benchmark phases, there was no visibility into server performance until the final results were printed. This made it difficult to monitor progress or detect issues early during multi-hour benchmark runs. This adds a background thread that periodically reports QPS, hit ratio, and operation counts to stdout. The reporting respects phase transitions (warmup vs benchmark) and resets its interval calculations when phases change. The interval is configurable via `--stats_interval_seconds` flag (default 10s, 0 to disable). Reviewed By: excelle08 Differential Revision: D94508507 fbshipit-source-id: 5f092b42752a628c2d844f4d5f1afc474a48e2e9
1 parent 6193213 commit 6ab9029

3 files changed

Lines changed: 126 additions & 0 deletions

File tree

packages/ucache_bench/server/UcacheBenchServer.cpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <folly/Format.h>
1111
#include <folly/portability/GFlags.h>
12+
#include <chrono>
1213

1314
#include "cachelib/allocator/CacheAllocator.h"
1415
#include "cachelib/allocator/HitsPerSlabStrategy.h"
@@ -24,6 +25,10 @@ UcacheBenchServer::UcacheBenchServer(const UcacheBenchConfig& config)
2425
setupCacheLib();
2526
}
2627

28+
UcacheBenchServer::~UcacheBenchServer() {
29+
stopPeriodicStats();
30+
}
31+
2732
void UcacheBenchServer::setupCacheLib() {
2833
// CacheLib always requires DRAM cache initialization
2934
// Navy (NVM/SSD cache) is optionally enabled based on navy_cache_size_mb
@@ -476,5 +481,100 @@ void UcacheBenchServer::printFinalResults(double benchmarkDurationSec) const {
476481
}
477482
}
478483

484+
void UcacheBenchServer::startPeriodicStats(uint32_t intervalSec) {
485+
if (intervalSec == 0) {
486+
return;
487+
}
488+
statsRunning_.store(true);
489+
statsThread_ =
490+
std::thread([this, intervalSec]() { periodicStatsLoop(intervalSec); });
491+
}
492+
493+
void UcacheBenchServer::stopPeriodicStats() {
494+
if (statsRunning_.load()) {
495+
statsRunning_.store(false);
496+
statsCv_.notify_all();
497+
if (statsThread_.joinable()) {
498+
statsThread_.join();
499+
}
500+
}
501+
}
502+
503+
void UcacheBenchServer::periodicStatsLoop(uint32_t intervalSec) {
504+
// Previous snapshot for computing interval QPS
505+
uint64_t prevTotalOps = 0;
506+
auto prevTime = std::chrono::steady_clock::now();
507+
auto phaseStartTime = prevTime;
508+
TrackingPhase prevPhase = TrackingPhase::NONE;
509+
510+
while (statsRunning_.load()) {
511+
{
512+
std::unique_lock<std::mutex> lock(statsMutex_);
513+
statsCv_.wait_for(lock, std::chrono::seconds(intervalSec), [this]() {
514+
return !statsRunning_.load();
515+
});
516+
}
517+
518+
if (!statsRunning_.load()) {
519+
break;
520+
}
521+
522+
auto phase = currentPhase_.load();
523+
if (phase == TrackingPhase::NONE) {
524+
continue;
525+
}
526+
527+
// Reset snapshot on phase transition
528+
if (phase != prevPhase) {
529+
prevTotalOps = 0;
530+
prevTime = std::chrono::steady_clock::now();
531+
phaseStartTime = prevTime;
532+
prevPhase = phase;
533+
}
534+
535+
const PhaseMetrics& metrics =
536+
(phase == TrackingPhase::WARMUP) ? warmupMetrics_ : benchmarkMetrics_;
537+
538+
uint64_t getReqs = metrics.getRequests.load(std::memory_order_relaxed);
539+
uint64_t getHits = metrics.getHits.load(std::memory_order_relaxed);
540+
uint64_t setReqs = metrics.setRequests.load(std::memory_order_relaxed);
541+
uint64_t deleteReqs =
542+
metrics.deleteRequests.load(std::memory_order_relaxed);
543+
uint64_t totalOps = getReqs + setReqs + deleteReqs;
544+
545+
auto now = std::chrono::steady_clock::now();
546+
double intervalElapsed =
547+
std::chrono::duration<double>(now - prevTime).count();
548+
double phaseElapsed =
549+
std::chrono::duration<double>(now - phaseStartTime).count();
550+
551+
double intervalQps = (intervalElapsed > 0)
552+
? (totalOps - prevTotalOps) / intervalElapsed
553+
: 0.0;
554+
double avgQps = (phaseElapsed > 0) ? totalOps / phaseElapsed : 0.0;
555+
double hitRatio = (getReqs > 0) ? (100.0 * getHits / getReqs) : 0.0;
556+
557+
const char* phaseName =
558+
(phase == TrackingPhase::WARMUP) ? "WARMUP" : "BENCHMARK";
559+
560+
printf(
561+
"[Server %s] %.0fs elapsed | QPS: %.0f (avg: %.0f) | "
562+
"hit_ratio: %.2f%% | ops: %lu (GET: %lu, SET: %lu, DEL: %lu)\n",
563+
phaseName,
564+
phaseElapsed,
565+
intervalQps,
566+
avgQps,
567+
hitRatio,
568+
totalOps,
569+
getReqs,
570+
setReqs,
571+
deleteReqs);
572+
fflush(stdout);
573+
574+
prevTotalOps = totalOps;
575+
prevTime = now;
576+
}
577+
}
578+
479579
} // namespace ucachebench
480580
} // namespace facebook

packages/ucache_bench/server/UcacheBenchServer.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
#include <cachelib/allocator/CacheAllocator.h>
1111
#include <folly/futures/Future.h>
1212
#include <atomic>
13+
#include <condition_variable>
1314
#include <memory>
15+
#include <mutex>
1416
#include <string>
17+
#include <thread>
1518
#ifdef OSS_BUILD
1619
#include "UcacheBenchMessages.h"
1720
#else
@@ -126,6 +129,7 @@ struct UcacheBenchConfig {
126129
class UcacheBenchServer {
127130
public:
128131
explicit UcacheBenchServer(const UcacheBenchConfig& config);
132+
~UcacheBenchServer();
129133

130134
// Request handlers using Carbon protocol
131135
folly::SemiFuture<UcbGetReply> processUcbGet(const UcbGetRequest& req);
@@ -163,6 +167,10 @@ class UcacheBenchServer {
163167
// Print final results in parseable format for benchpress
164168
void printFinalResults(double benchmarkDurationSec) const;
165169

170+
// Periodic stats reporting
171+
void startPeriodicStats(uint32_t intervalSec);
172+
void stopPeriodicStats();
173+
166174
private:
167175
void setupCacheLib();
168176

@@ -171,6 +179,9 @@ class UcacheBenchServer {
171179
void recordSet();
172180
void recordDelete();
173181

182+
// Periodic stats thread function
183+
void periodicStatsLoop(uint32_t intervalSec);
184+
174185
UcacheBenchConfig config_;
175186
std::unique_ptr<CacheAllocator> cache_;
176187
PoolId poolId_{};
@@ -179,6 +190,12 @@ class UcacheBenchServer {
179190
std::atomic<TrackingPhase> currentPhase_{TrackingPhase::NONE};
180191
PhaseMetrics warmupMetrics_;
181192
PhaseMetrics benchmarkMetrics_;
193+
194+
// Periodic stats thread
195+
std::thread statsThread_;
196+
std::atomic<bool> statsRunning_{false};
197+
std::mutex statsMutex_;
198+
std::condition_variable statsCv_;
182199
};
183200

184201
} // namespace ucachebench

packages/ucache_bench/server/main.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ DEFINE_uint32(
3939
600,
4040
"Timeout in seconds for waiting for clients (0 = no timeout)");
4141
DEFINE_bool(verbose, false, "Enable verbose logging");
42+
DEFINE_uint32(
43+
stats_interval_seconds,
44+
10,
45+
"Periodic stats reporting interval in seconds during warmup/benchmark (0 = disable)");
4246

4347
// CacheLib configuration flags
4448
DEFINE_uint64(
@@ -376,6 +380,9 @@ int main(int argc, char** argv) {
376380
});
377381

378382
adminServer->start();
383+
384+
// Start periodic server-side stats reporting
385+
server->startPeriodicStats(FLAGS_stats_interval_seconds);
379386
}
380387

381388
// If admin server is enabled, wait for it to complete
@@ -385,6 +392,8 @@ int main(int argc, char** argv) {
385392
if (!completed) {
386393
printf("Admin server timed out or failed\n");
387394
}
395+
// Stop periodic stats before printing final results
396+
server->stopPeriodicStats();
388397
// Stop the admin server
389398
adminServer->stop();
390399
} else {

0 commit comments

Comments
 (0)