102 lines
4.0 KiB
C++
102 lines
4.0 KiB
C++
|
|
#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();
|
||
|
|
}
|