Files
Towards/modules/peer_to_peer/tests/PeerNodeBenchmark.cpp
T

102 lines
4.0 KiB
C++
Raw Normal View History

2026-07-18 14:31:15 +02:00
#include <catch2/catch_test_macros.hpp>
#include "TestPeerNode.hpp"
#include "QuicrPeerLink.hpp"
#include "WorldStepProcessor.hpp"
#include <barrier>
#include <limits>
#include <memory>
#include <spdlog/spdlog.h>
#include <thread>
#include <vector>
using namespace tw::p2p;
// ── constants ─────────────────────────────────────────────────────────────────
static constexpr int NUM_PEERS = 5;
static constexpr uint16_t BASE_PORT = 7600;
static constexpr int NUM_TICKS = 120; // 2 s at 60 Hz
static constexpr auto TICK_PERIOD =
std::chrono::microseconds(static_cast<int>(WorldStepProcessor::FIXED_DELTA_S * 1e6));
static const char* REGISTRY_PATH = "/tmp/tw_p2p_bench_registry.csv";
// ── helpers ───────────────────────────────────────────────────────────────────
static void cleanup_files() {
::unlink(REGISTRY_PATH);
for (int i = 1; i <= NUM_PEERS; ++i) {
std::string path = std::string("/tmp/peer_node_") + std::to_string(i) + ".csv";
::unlink(path.c_str());
}
}
// ── benchmark test ────────────────────────────────────────────────────────────
TEST_CASE("10 peers synchronize and write per-entity CSV", "[p2p][benchmark]") {
cleanup_files();
// Phase barrier: all nodes must register before any node connects.
std::barrier registered(NUM_PEERS);
struct Result { uint64_t stepped = 0; uint64_t rollbacks = 0; };
std::vector<Result> results(NUM_PEERS);
auto run_node = [&](int idx) {
const uint32_t id = static_cast<uint32_t>(idx + 1);
const uint16_t port = BASE_PORT + static_cast<uint16_t>(idx);
auto link = std::make_unique<QuicrPeerLink>(id, port);
std::string csv = "/tmp/peer_node_" + std::to_string(id) + ".quicr.csv";
auto node = std::make_unique<TestPeerNode>(id, port, std::move(link), REGISTRY_PATH, csv);
node->register_self();
registered.arrive_and_wait(); // block until every peer has registered
node->discover_and_connect();
node->wait_for_connections(NUM_PEERS - 1, std::chrono::milliseconds(3000));
auto deadline = std::chrono::steady_clock::now();
for (int i = 0; i < NUM_TICKS; ++i) {
deadline += TICK_PERIOD;
node->tick();
std::this_thread::sleep_until(deadline);
}
results[idx].stepped = node->stepped_frames();
results[idx].rollbacks = node->rollbacks();
spdlog::info("[p2p] node {:2d} stepped={:4} rollbacks={:4} rollback_rate={:.1f}%",
id,
results[idx].stepped,
results[idx].rollbacks,
100.0 * static_cast<double>(results[idx].rollbacks) /
static_cast<double>(results[idx].stepped + 1));
};
std::vector<std::thread> threads;
threads.reserve(NUM_PEERS);
for (int i = 0; i < NUM_PEERS; ++i)
threads.emplace_back(run_node, i);
for (auto& t : threads) t.join();
// ── assertions ────────────────────────────────────────────────────────────
uint64_t min_stepped = std::numeric_limits<uint64_t>::max();
uint64_t max_stepped = 0;
for (int i = 0; i < NUM_PEERS; ++i) {
REQUIRE(results[i].stepped > 0);
min_stepped = std::min(min_stepped, results[i].stepped);
max_stepped = std::max(max_stepped, results[i].stepped);
}
double sync_ratio =
static_cast<double>(min_stepped) / static_cast<double>(max_stepped);
REQUIRE(sync_ratio > 0.80);
cleanup_files();
}