#pragma once #include #include #include #include #include #include // clock_gettime, CLOCK_THREAD_CPUTIME_ID namespace kpn { using clock_t = std::chrono::steady_clock; using duration_t = std::chrono::duration; // milliseconds // ── Per-channel statistics ──────────────────────────────────────────────────── struct ChannelStats { std::atomic pushes{0}; std::atomic drops{0}; std::atomic overflows{0}; std::atomic pops{0}; std::atomic peak_fill{0}; ChannelStats() = default; ChannelStats(const ChannelStats&) = delete; ChannelStats& operator=(const ChannelStats&) = delete; void record_push(std::size_t current_fill) { pushes.fetch_add(1, std::memory_order_relaxed); std::size_t prev = peak_fill.load(std::memory_order_relaxed); while (current_fill > prev && !peak_fill.compare_exchange_weak(prev, current_fill, std::memory_order_relaxed, std::memory_order_relaxed)) ; } void record_drop() { drops.fetch_add(1, std::memory_order_relaxed); } void record_overflow() { overflows.fetch_add(1, std::memory_order_relaxed); } void record_pop() { pops.fetch_add(1, std::memory_order_relaxed); } }; // ── Per-node statistics ─────────────────────────────────────────────────────── struct NodeStats { std::atomic frames_processed{0}; // Wall-clock execution time EMA — warmup mean for first WARMUP_FRAMES, // then EMA alpha=0.1. Stored as integer microseconds for atomic updates. static constexpr int WARMUP_FRAMES = 5; std::atomic ema_exec_us{0}; std::atomic max_exec_us{0}; std::atomic total_blocked_us{0}; // Thread CPU time — actual CPU consumed by this node's thread, // measured via CLOCK_THREAD_CPUTIME_ID. Excludes time sleeping or // blocked on mutexes/channels. Sampled once per frame. std::atomic total_cpu_us{0}; // cumulative CPU µs consumed NodeStats() = default; NodeStats(const NodeStats&) = delete; NodeStats& operator=(const NodeStats&) = delete; // Call at the start of run_loop to capture thread CPU baseline. // Returns the raw timespec for use in record_exec. static struct timespec cpu_now() { struct timespec ts{}; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); return ts; } static int64_t timespec_us(const struct timespec& ts) { return static_cast(ts.tv_sec) * 1'000'000 + static_cast(ts.tv_nsec) / 1'000; } void record_exec(duration_t exec_time, duration_t blocked_time, const struct timespec& cpu_before, const struct timespec& cpu_after) { frames_processed.fetch_add(1, std::memory_order_relaxed); int64_t us = static_cast(exec_time.count() * 1000.0); uint64_t n = frames_processed.load(std::memory_order_relaxed); int64_t prev = ema_exec_us.load(std::memory_order_relaxed); int64_t next = (n <= static_cast(WARMUP_FRAMES)) ? prev + (us - prev) / static_cast(n) : prev + (us - prev) / 10; ema_exec_us.store(next, std::memory_order_relaxed); int64_t cur_max = max_exec_us.load(std::memory_order_relaxed); if (us > cur_max) max_exec_us.store(us, std::memory_order_relaxed); int64_t blocked_us = static_cast(blocked_time.count() * 1000.0); total_blocked_us.fetch_add(blocked_us, std::memory_order_relaxed); int64_t cpu_delta = timespec_us(cpu_after) - timespec_us(cpu_before); if (cpu_delta > 0) total_cpu_us.fetch_add(cpu_delta, std::memory_order_relaxed); } }; // ── Snapshot for reporting (copyable, taken by watchdog) ───────────────────── struct ChannelSnapshot { std::string name; std::size_t capacity; std::size_t current_fill; std::size_t peak_fill; uint64_t pushes; uint64_t drops; uint64_t overflows; uint64_t pops; std::size_t item_bytes; // sizeof(T) for the stored type — set by Channel double fill_pct() const { return capacity ? 100.0 * current_fill / capacity : 0.0; } double peak_pct() const { return capacity ? 100.0 * peak_fill / capacity : 0.0; } // Bandwidth in MB/s: bytes transferred / elapsed seconds double bandwidth_mbs(double elapsed_s) const { if (elapsed_s <= 0.0 || item_bytes == 0) return 0.0; return static_cast(pushes * item_bytes) / elapsed_s / 1e6; } }; struct NodeSnapshot { std::string name; uint64_t frames_processed; double ema_exec_ms; double max_exec_ms; double total_blocked_ms; double throughput_fps; double total_cpu_ms; // cumulative CPU time consumed by this node's thread double cpu_util_pct; // exec_ms / (exec_ms + blocked_ms) * 100 }; // ── Resource statistics + snapshot ─────────────────────────────────────────── struct ResourceSnapshot { std::string name; uint64_t acquisitions; double avg_wait_ms; uint64_t peak_waiters; uint64_t current_waiters; bool held; }; struct IResourceProbe { virtual ~IResourceProbe() = default; virtual ResourceSnapshot snapshot(const std::string& name) const = 0; }; } // namespace kpn