group2 0.1.0
CSE 125 Group 2
Loading...
Searching...
No Matches
Profiler.hpp File Reference

Lightweight server-side scoped profiler. More...

#include <SDL3/SDL.h>
#include <array>
#include <atomic>
#include <cstdint>
#include <functional>
#include <limits>
Include dependency graph for Profiler.hpp:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

struct  group2::perf::PerScopeStats
 Per-scope, all-thread atomic counters. More...
struct  group2::perf::NetworkCounters
 Per-tick network counters maintained by the network code. More...
struct  group2::perf::Snapshot
 Globally-visible snapshot returned to the aggregator callback. More...
struct  group2::perf::Snapshot::ScopeSummary
 Per-scope summary, filled for [0, scopeCount). More...
class  group2::perf::ScopeTimer
 RAII scoped timer. More...

Namespaces

namespace  group2
namespace  group2::perf

Macros

#define GROUP2_PROF_SCOPE(label)
 Drop-in scoped timer.

Typedefs

using group2::perf::ScopeId = std::uint16_t
 Dense small id used to index the global stats table.

Functions

ScopeId group2::perf::registerScope (const char *name)
 Register (or look up) a scope name and return its dense id.
const char * group2::perf::scopeName (ScopeId id)
 Returns the human-readable name a ScopeId was registered with, or "" if id is out of range.
std::size_t group2::perf::scopeCount ()
 Returns the highest registered id + 1.
void group2::perf::initFromEnv ()
 Initialize from environment variables.
void group2::perf::startAggregator (std::function< void(const Snapshot &)> cb)
 Spawn the 1 Hz aggregator thread.
void group2::perf::stopAggregator ()
 Stop the aggregator and join its thread. Idempotent.
void group2::perf::recordSample (ScopeId id, std::uint64_t ticks) noexcept
 Recording entry point — public so unit tests can invoke it directly without a real ScopeTimer.
void group2::perf::tickEnd (std::uint64_t tickWallNs) noexcept
 Tick boundary marker — call once per server tick() end.
NetworkCountersgroup2::perf::net ()
 Network counter accessor. Hot-path code increments these directly.
std::uint64_t group2::perf::ticksToNs (std::uint64_t ticks) noexcept
 Convenience: convert SDL performance-counter ticks to nanoseconds.

Variables

constexpr std::size_t group2::perf::k_maxScopes = 64
 Compile-time caps.
constexpr std::size_t group2::perf::k_histogramBuckets = 32
 Histogram bucket count.
constexpr ScopeId group2::perf::k_invalidScope = static_cast<ScopeId>(-1)

Detailed Description

Lightweight server-side scoped profiler.

Goal: measure where the per-tick budget goes at 150–500 connected bots without perturbing the measurement. Cost per scope is ~70 ns (two SDL_GetPerformanceCounter reads + a handful of relaxed atomic updates). At ~14 scopes per tick × 128 Hz that is ~125 µs/s of pure profiler overhead — a measurement noise floor we treat as the empty cost of having the system on at all.

Layered on top:

  • GROUP2_PROF_SCOPE("name"): drop-in scoped timer.
  • Profiler::tickEnd(): call once per server tick from the game thread; updates the per-tick wall-clock counter that drives the 1 Hz aggregator.
  • Profiler::startAggregator(loggerCallback): spawns a worker thread that wakes once per second, snapshots and zeros the scope stats + network counters, and invokes the callback with a structured Snapshot. Callers route the snapshot to SDL_Log + an optional CSV file.

Toggling: GROUP2_SERVER_PROFILE=1 enables sample collection at startup. With the env var unset the macro still expands but every ScopeTimer ctor early-outs on the cached enabled.load(), so the cost collapses to one relaxed atomic load per scope (~1 ns).

Thread-safety: every counter in this header is std::atomic with relaxed ordering. We deliberately do not use a per-thread buffer flushed at aggregation time — the simpler all-atomic path is correct under any future parallel-system layout (PR-3+) and the extra atomic-ops cost is below the SDL clock-read cost we pay anyway.

Macro Definition Documentation

◆ GROUP2_PROF_SCOPE

#define GROUP2_PROF_SCOPE ( label)
Value:
::group2::perf::ScopeTimer GROUP2_PROF_CAT(_gpScope, __LINE__) \
{ \
[]() noexcept -> ::group2::perf::ScopeId { \
static const ::group2::perf::ScopeId k_id = ::group2::perf::registerScope(label); \
return k_id; \
}() \
}
RAII scoped timer.
Definition Profiler.hpp:184
ScopeId registerScope(const char *name)
Register (or look up) a scope name and return its dense id.
Definition Profiler.cpp:189
std::uint16_t ScopeId
Dense small id used to index the global stats table.
Definition Profiler.hpp:59

Drop-in scoped timer.

The scope name is registered exactly once per call site (cached via a function-local static const).

Note: we use __LINE__ rather than __COUNTER__ because the latter is a compiler extension flagged by -Wpedantic / -Wc2y-extensions. This means two GROUP2_PROF_SCOPE calls on the same source line would collide; in practice every scope macro lives on its own line inside a brace block, so the collision can't happen.

Usage:

void tick() {
// ... work ...
}
#define GROUP2_PROF_SCOPE(label)
Drop-in scoped timer.
Definition Profiler.hpp:238