Corium 1.1.0
High-Performance Zero-Heap C++20 MPSC Application Runtime
Loading...
Searching...
No Matches
ProfilerPolicies.hpp
Go to the documentation of this file.
1
7#pragma once
8
9#include <atomic>
10#include <chrono>
11#include <cstddef>
12#include <cstdint>
13#include <ostream>
14
17
18namespace corium::profiler {
19
22 constexpr NullProfiler() noexcept = default;
23
24 template <typename EventVariant>
25 void onEventPosted(const EventVariant&, uint8_t) noexcept {}
26
27 template <typename EventVariant>
29 const EventVariant&,
30 uint8_t,
31 uint64_t /*postTimeNs*/,
32 uint64_t /*dispatchTimeNs*/,
33 uint64_t /*finishTimeNs*/
34 ) noexcept {}
35
36 [[nodiscard]] static constexpr uint64_t nowNs() noexcept { return 0; }
37
39 void recordPostTime(uint64_t) noexcept {}
40
42 [[nodiscard]] uint64_t takePostTime() noexcept { return 0; }
43};
44
45// ─────────────────────────────────────────────────────────────────────────────
46// Internal: parallel timestamp ring buffer for tracking per-event post times.
47//
48// Uses a separate MpscRingBuffer<uint64_t, Capacity> that is pushed in lockstep
49// with the main event queue. Because the event bus is MPSC and dispatch is
50// single-consumer (same thread that calls pump()), timestamps arrive and are
51// consumed in FIFO order, so the i-th timestamp always belongs to the i-th event.
52//
53// Zero-overhead when used with NullProfiler (the entire type is not instantiated).
54// ─────────────────────────────────────────────────────────────────────────────
55template <std::size_t Capacity = 1024>
57public:
59 void recordPostTime(uint64_t postNs) noexcept
60 {
61 // Best-effort: if the timestamp queue is full (e.g. profiler not being read),
62 // drop the timestamp rather than blocking or corrupting the event queue.
63 _timestamps.tryPush(postNs);
64 }
65
68 [[nodiscard]] uint64_t takePostTime() noexcept
69 {
70 uint64_t ts = 0;
71 _timestamps.tryPop(ts);
72 return ts;
73 }
74
75private:
77};
78
79// ─────────────────────────────────────────────────────────────────────────────
80
86template <std::size_t QueueCapacity = 1024>
88public:
89 LatencyTracker() noexcept = default;
90
91 [[nodiscard]] static uint64_t nowNs() noexcept
92 {
93 return static_cast<uint64_t>(
94 std::chrono::duration_cast<std::chrono::nanoseconds>(
95 ProfilerClock::now().time_since_epoch()
96 ).count()
97 );
98 }
99
102 void recordPostTime(uint64_t postNs) noexcept
103 {
104 if (!_enabled.load(std::memory_order_relaxed)) return;
105 _postTimestamps.recordPostTime(postNs);
106 }
107
109 [[nodiscard]] uint64_t takePostTime() noexcept
110 {
111 return _postTimestamps.takePostTime();
112 }
113
114 template <typename EventVariant>
115 void onEventPosted(const EventVariant&, uint8_t) noexcept
116 {
117 if (!_enabled.load(std::memory_order_relaxed)) return;
118 _totalPosted.fetch_add(1, std::memory_order_relaxed);
119 }
120
121 template <typename EventVariant>
123 const EventVariant&,
124 uint8_t,
125 uint64_t postTimeNs,
126 uint64_t dispatchTimeNs,
127 uint64_t finishTimeNs
128 ) noexcept
129 {
130 if (!_enabled.load(std::memory_order_relaxed)) return;
131
132 const uint64_t queueLatencyNs = (dispatchTimeNs > postTimeNs) ? (dispatchTimeNs - postTimeNs) : 0;
133 const uint64_t execDurationNs = (finishTimeNs > dispatchTimeNs) ? (finishTimeNs - dispatchTimeNs) : 0;
134
135 _totalDispatched.fetch_add(1, std::memory_order_relaxed);
136 _totalQueueLatencyNs.fetch_add(queueLatencyNs, std::memory_order_relaxed);
137 _totalExecDurationNs.fetch_add(execDurationNs, std::memory_order_relaxed);
138
139 // Update Max Latency
140 uint64_t currentMaxLat = _maxQueueLatencyNs.load(std::memory_order_relaxed);
141 while (queueLatencyNs > currentMaxLat &&
142 !_maxQueueLatencyNs.compare_exchange_weak(currentMaxLat, queueLatencyNs, std::memory_order_relaxed)) {}
143
144 // Update Min Latency
145 uint64_t currentMinLat = _minQueueLatencyNs.load(std::memory_order_relaxed);
146 while (queueLatencyNs < currentMinLat &&
147 !_minQueueLatencyNs.compare_exchange_weak(currentMinLat, queueLatencyNs, std::memory_order_relaxed)) {}
148
149 // Update Max Execution Duration
150 uint64_t currentMaxExec = _maxExecDurationNs.load(std::memory_order_relaxed);
151 while (execDurationNs > currentMaxExec &&
152 !_maxExecDurationNs.compare_exchange_weak(currentMaxExec, execDurationNs, std::memory_order_relaxed)) {}
153 }
154
156 void setEnabled(bool enabled) noexcept
157 {
158 _enabled.store(enabled, std::memory_order_release);
159 }
160
162 void enable() noexcept { setEnabled(true); }
163
165 void disable() noexcept { setEnabled(false); }
166
168 [[nodiscard]] bool isEnabled() const noexcept
169 {
170 return _enabled.load(std::memory_order_acquire);
171 }
172
174 [[nodiscard]] uint64_t totalPosted() const noexcept
175 {
176 return _totalPosted.load(std::memory_order_relaxed);
177 }
178
180 [[nodiscard]] uint64_t totalDispatched() const noexcept
181 {
182 return _totalDispatched.load(std::memory_order_relaxed);
183 }
184
186 [[nodiscard]] double minQueueLatencyUs() const noexcept
187 {
188 const uint64_t val = _minQueueLatencyNs.load(std::memory_order_relaxed);
189 return val == UINT64_MAX ? 0.0 : static_cast<double>(val) / 1000.0;
190 }
191
193 [[nodiscard]] double maxQueueLatencyUs() const noexcept
194 {
195 return static_cast<double>(_maxQueueLatencyNs.load(std::memory_order_relaxed)) / 1000.0;
196 }
197
199 [[nodiscard]] double averageQueueLatencyUs() const noexcept
200 {
201 const uint64_t count = _totalDispatched.load(std::memory_order_relaxed);
202 if (count == 0) return 0.0;
203 return static_cast<double>(_totalQueueLatencyNs.load(std::memory_order_relaxed)) / (static_cast<double>(count) * 1000.0);
204 }
205
207 [[nodiscard]] double maxExecutionDurationUs() const noexcept
208 {
209 return static_cast<double>(_maxExecDurationNs.load(std::memory_order_relaxed)) / 1000.0;
210 }
211
213 [[nodiscard]] double averageExecutionDurationUs() const noexcept
214 {
215 const uint64_t count = _totalDispatched.load(std::memory_order_relaxed);
216 if (count == 0) return 0.0;
217 return static_cast<double>(_totalExecDurationNs.load(std::memory_order_relaxed)) / (static_cast<double>(count) * 1000.0);
218 }
219
221 void resetStats() noexcept
222 {
223 _totalPosted.store(0, std::memory_order_relaxed);
224 _totalDispatched.store(0, std::memory_order_relaxed);
225 _totalQueueLatencyNs.store(0, std::memory_order_relaxed);
226 _totalExecDurationNs.store(0, std::memory_order_relaxed);
227 _maxQueueLatencyNs.store(0, std::memory_order_relaxed);
228 _minQueueLatencyNs.store(UINT64_MAX, std::memory_order_relaxed);
229 _maxExecDurationNs.store(0, std::memory_order_relaxed);
230 }
231
232private:
233 PostTimestampQueue<QueueCapacity> _postTimestamps;
234
235 std::atomic<bool> _enabled{true};
236 std::atomic<uint64_t> _totalPosted{0};
237 std::atomic<uint64_t> _totalDispatched{0};
238 std::atomic<uint64_t> _totalQueueLatencyNs{0};
239 std::atomic<uint64_t> _totalExecDurationNs{0};
240 std::atomic<uint64_t> _maxQueueLatencyNs{0};
241 std::atomic<uint64_t> _minQueueLatencyNs{UINT64_MAX};
242 std::atomic<uint64_t> _maxExecDurationNs{0};
243};
244
250template <std::size_t BufferCapacity = 256, std::size_t QueueCapacity = 1024>
251class FlightRecorderProfiler : public LatencyTracker<QueueCapacity> {
252public:
254
255 template <typename EventVariant>
257 const EventVariant& event,
258 uint8_t priority,
259 uint64_t postTimeNs,
260 uint64_t dispatchTimeNs,
261 uint64_t finishTimeNs
262 ) noexcept
263 {
264 if (!this->isEnabled()) return;
265
266 LatencyTracker<QueueCapacity>::onEventDispatched(event, priority, postTimeNs, dispatchTimeNs, finishTimeNs);
267
268 const std::size_t typeIndex = event.index();
269 const char* name = "Event";
270
271 _flightRecorder.record(typeIndex, name, postTimeNs, dispatchTimeNs, finishTimeNs, priority);
272 }
273
275 [[nodiscard]] const FlightRecorder<BufferCapacity>& flightRecorder() const noexcept
276 {
277 return _flightRecorder;
278 }
279
281 void exportChromeTracingJson(std::ostream& os) const
282 {
283 _flightRecorder.exportChromeTracingJson(os);
284 }
285
286private:
287 FlightRecorder<BufferCapacity> _flightRecorder;
288};
289
290} // namespace corium::profiler
Circular in-memory telemetry buffer with Chrome Tracing JSON export.
Lock-free Multi-Producer Single-Consumer (MPSC) bounded ring buffer based on Dmitry Vyukov's algorith...
Lock-free Multiple-Producer, Single-Consumer (MPSC) RingBuffer. Implements Dmitry Vyukov's algorithm ...
Definition MpscRingBuffer.hpp:33
bool tryPop(T &result)
Pop an item from the queue (Single-Consumer only).
Definition MpscRingBuffer.hpp:115
PushResult tryPush(Args &&... args)
Push an item into the queue (Multi-Producer thread safe).
Definition MpscRingBuffer.hpp:86
Combined Flight Recorder and Latency Tracker Profiler. Records historical event traces into a circula...
Definition ProfilerPolicies.hpp:251
void exportChromeTracingJson(std::ostream &os) const
Export flight recorder traces to Chrome Tracing JSON.
Definition ProfilerPolicies.hpp:281
void onEventDispatched(const EventVariant &event, uint8_t priority, uint64_t postTimeNs, uint64_t dispatchTimeNs, uint64_t finishTimeNs) noexcept
Definition ProfilerPolicies.hpp:256
const FlightRecorder< BufferCapacity > & flightRecorder() const noexcept
Access reference to underlying circular flight recorder.
Definition ProfilerPolicies.hpp:275
Zero-heap circular flight recorder storing the last N event telemetry records. Thread-safe for multip...
Definition FlightRecorder.hpp:57
Real-time event latency and performance statistics tracker. Zero dynamic memory allocation....
Definition ProfilerPolicies.hpp:87
void enable() noexcept
Enable runtime profiling.
Definition ProfilerPolicies.hpp:162
bool isEnabled() const noexcept
Check if runtime profiling is enabled.
Definition ProfilerPolicies.hpp:168
LatencyTracker() noexcept=default
void recordPostTime(uint64_t postNs) noexcept
Record the wall-clock post timestamp for the event being pushed now. Called by EventBus::post() immed...
Definition ProfilerPolicies.hpp:102
double maxExecutionDurationUs() const noexcept
Maximum handler execution duration in microseconds.
Definition ProfilerPolicies.hpp:207
void disable() noexcept
Disable runtime profiling.
Definition ProfilerPolicies.hpp:165
double maxQueueLatencyUs() const noexcept
Maximum queue latency in microseconds.
Definition ProfilerPolicies.hpp:193
void onEventPosted(const EventVariant &, uint8_t) noexcept
Definition ProfilerPolicies.hpp:115
uint64_t takePostTime() noexcept
Pop and return the oldest post timestamp (called at dispatch time by EventBus).
Definition ProfilerPolicies.hpp:109
double averageQueueLatencyUs() const noexcept
Average queue latency in microseconds.
Definition ProfilerPolicies.hpp:199
uint64_t totalPosted() const noexcept
Total count of posted events.
Definition ProfilerPolicies.hpp:174
void setEnabled(bool enabled) noexcept
Enable or disable runtime latency profiling.
Definition ProfilerPolicies.hpp:156
double averageExecutionDurationUs() const noexcept
Average handler execution duration in microseconds.
Definition ProfilerPolicies.hpp:213
static uint64_t nowNs() noexcept
Definition ProfilerPolicies.hpp:91
void onEventDispatched(const EventVariant &, uint8_t, uint64_t postTimeNs, uint64_t dispatchTimeNs, uint64_t finishTimeNs) noexcept
Definition ProfilerPolicies.hpp:122
double minQueueLatencyUs() const noexcept
Minimum queue latency in microseconds.
Definition ProfilerPolicies.hpp:186
uint64_t totalDispatched() const noexcept
Total count of dispatched events.
Definition ProfilerPolicies.hpp:180
void resetStats() noexcept
Reset all accumulated statistics.
Definition ProfilerPolicies.hpp:221
Definition ProfilerPolicies.hpp:56
void recordPostTime(uint64_t postNs) noexcept
Record the post timestamp for an event being pushed into the event queue.
Definition ProfilerPolicies.hpp:59
uint64_t takePostTime() noexcept
Pop and return the oldest recorded post timestamp (called at dispatch time).
Definition ProfilerPolicies.hpp:68
Definition FlightRecorder.hpp:16
Default Profiler Policy: Zero-overhead, completely compiled out by inline empty functions.
Definition ProfilerPolicies.hpp:21
constexpr NullProfiler() noexcept=default
void onEventPosted(const EventVariant &, uint8_t) noexcept
Definition ProfilerPolicies.hpp:25
void onEventDispatched(const EventVariant &, uint8_t, uint64_t, uint64_t, uint64_t) noexcept
Definition ProfilerPolicies.hpp:28
static constexpr uint64_t nowNs() noexcept
Definition ProfilerPolicies.hpp:36
uint64_t takePostTime() noexcept
No-op: always returns 0 (NullProfiler has no timestamps).
Definition ProfilerPolicies.hpp:42
void recordPostTime(uint64_t) noexcept
No-op: NullProfiler does not track post timestamps.
Definition ProfilerPolicies.hpp:39