tess 1.0.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
diagnostics.h
1#pragma once
2
3#include <cstddef>
4#include <cstdint>
5#include <limits>
6
7// TESS_ENABLE_DIAGNOSTICS changes public TYPES, not just statements:
8// PathCounters, TraceBuffer, WarningSink and six more exist only when it is
9// defined, and the diagnostics accessors change signature with them. A
10// program that defines it for some translation units and not others
11// violates the one-definition rule with no diagnostic on any compiler --
12// the layouts simply disagree at link time. Define it for a whole binary
13// or not at all.
14//
15// The pragma gives MSVC a link-time check; GCC and Clang have no equivalent
16// mechanism, so consistency there is the build system's job. This mirrors
17// what core/config.h does for the exception mode and core/capacity.h for
18// the internal capacity hook.
19#if defined(_MSC_VER)
20#if defined(TESS_ENABLE_DIAGNOSTICS)
21#pragma detect_mismatch("tess_diagnostics_mode", "enabled")
22#else
23#pragma detect_mismatch("tess_diagnostics_mode", "disabled")
24#endif
25#endif
26
27#if defined(TESS_ENABLE_DIAGNOSTICS)
29#define TESS_DIAGNOSTICS_ENABLED 1
31#define TESS_DIAGNOSTIC_ONLY(expr) \
32 do { \
33 expr; \
34 } while (false)
36#define TESS_DIAGNOSTIC_INC(counter) \
37 do { \
38 ++(counter); \
39 } while (false)
41#define TESS_DIAGNOSTIC_ADD(counter, value) \
42 do { \
43 (counter) += (value); \
44 } while (false)
46#define TESS_DIAG_EVENT(name) \
47 do { \
48 ::tess::diagnostics::event_##name(); \
49 } while (false)
51#define TESS_DIAG_EVENT_VALUE(name, value) \
52 do { \
53 ::tess::diagnostics::event_##name(value); \
54 } while (false)
55#else
56#define TESS_DIAGNOSTICS_ENABLED 0
57#define TESS_DIAGNOSTIC_ONLY(expr) \
58 do { \
59 } while (false)
60#define TESS_DIAGNOSTIC_INC(counter) \
61 do { \
62 } while (false)
63#define TESS_DIAGNOSTIC_ADD(counter, value) \
64 do { \
65 } while (false)
66#define TESS_DIAG_EVENT(name) \
67 do { \
68 } while (false)
69#define TESS_DIAG_EVENT_VALUE(name, value) \
70 do { \
71 } while (false)
72#endif
73
74namespace tess::diagnostics {
75
76#if TESS_DIAGNOSTICS_ENABLED
79 std::uint64_t scratch_clear_calls = 0;
80 std::uint64_t scratch_clear_nodes = 0;
81 std::uint64_t initializations = 0;
82 std::uint64_t start_passability_checks = 0;
83 std::uint64_t goal_passability_checks = 0;
84 std::uint64_t heap_pushes = 0;
85 std::uint64_t heap_pops = 0;
86 std::uint64_t stale_pops = 0;
87 std::uint64_t closed_pops = 0;
88 std::uint64_t neighbor_candidates = 0;
89 std::uint64_t passability_checks = 0;
90 std::uint64_t cost_reads = 0;
91 std::uint64_t blocked_neighbors = 0;
92 std::uint64_t closed_neighbors = 0;
93 std::uint64_t relax_attempts = 0;
94 std::uint64_t relax_successes = 0;
95 std::uint64_t touched_nodes = 0;
96 std::uint64_t heuristic_calls = 0;
97 std::uint64_t reconstructed_nodes = 0;
98
99 void reset() noexcept { *this = PathCounters{}; }
100};
101
104 std::uint64_t allocations = 0;
105 std::uint64_t allocation_bytes = 0;
106 std::uint64_t deallocations = 0;
107 std::uint64_t deallocation_bytes = 0;
108 // Best-effort retained-byte accounting. Unsized deallocation hooks pass
109 // zero and therefore cannot reduce this value; consumers that require exact
110 // live memory must supply sized allocator hooks.
111 std::uint64_t live_bytes = 0;
112 std::uint64_t peak_live_bytes = 0;
113
114 void reset() noexcept { *this = AllocationCounters{}; }
115};
116
119 std::uint64_t phase_calls = 0;
120 std::uint64_t phase_operations = 0;
121 std::uint64_t phase_invalid_ranges = 0;
122 std::uint64_t phase_failures = 0;
123 std::uint64_t partitioned_phase_calls = 0;
124 std::uint64_t dirty_partitions = 0;
125 std::uint64_t scoped_thread_calls = 0;
126 std::uint64_t scoped_thread_workers = 0;
127 std::uint64_t worker_pool_calls = 0;
128 std::uint64_t worker_pool_workers = 0;
129 std::uint64_t dirty_records_collected = 0;
130 std::uint64_t dirty_chunks_merged = 0;
131
132 void reset() noexcept { *this = QueuedPhaseCounters{}; }
133};
134
135inline thread_local PathCounters* active_path_counters = nullptr;
136inline thread_local AllocationCounters* active_allocation_counters = nullptr;
137inline thread_local std::uint64_t active_allocation_scope_id = 0;
138inline thread_local std::uint64_t next_allocation_scope_id = 1;
139inline thread_local QueuedPhaseCounters* active_queued_phase_counters = nullptr;
140
142class ScopedPathCounters {
143 public:
144 explicit ScopedPathCounters(PathCounters& counters) noexcept
145 : previous_{active_path_counters} {
146 active_path_counters = &counters;
147 }
148
149 ScopedPathCounters(const ScopedPathCounters&) = delete;
150 auto operator=(const ScopedPathCounters&) -> ScopedPathCounters& = delete;
151
152 ~ScopedPathCounters() { active_path_counters = previous_; }
153
154 private:
155 PathCounters* previous_;
156};
157
161class ScopedAllocationCounters {
162 public:
163 explicit ScopedAllocationCounters(AllocationCounters& counters) noexcept
164 : previous_{active_allocation_counters},
165 previous_scope_id_{active_allocation_scope_id},
166 scope_id_{next_allocation_scope_id++} {
167 if (next_allocation_scope_id == 0) {
168 next_allocation_scope_id = 1;
169 }
170 active_allocation_counters = &counters;
171 active_allocation_scope_id = scope_id_;
172 }
173
174 ScopedAllocationCounters(const ScopedAllocationCounters&) = delete;
175 auto operator=(const ScopedAllocationCounters&)
176 -> ScopedAllocationCounters& = delete;
177
178 ~ScopedAllocationCounters() {
179 active_allocation_counters = previous_;
180 active_allocation_scope_id = previous_scope_id_;
181 }
182
183 private:
184 AllocationCounters* previous_;
185 std::uint64_t previous_scope_id_;
186 std::uint64_t scope_id_;
187};
188
192class ScopedQueuedPhaseCounters {
193 public:
194 explicit ScopedQueuedPhaseCounters(QueuedPhaseCounters& counters) noexcept
195 : previous_{active_queued_phase_counters} {
196 active_queued_phase_counters = &counters;
197 }
198
199 ScopedQueuedPhaseCounters(const ScopedQueuedPhaseCounters&) = delete;
200 auto operator=(const ScopedQueuedPhaseCounters&)
201 -> ScopedQueuedPhaseCounters& = delete;
202
203 ~ScopedQueuedPhaseCounters() { active_queued_phase_counters = previous_; }
204
205 private:
206 QueuedPhaseCounters* previous_;
207};
208
210inline void record_allocation(std::size_t size) noexcept {
211 if (active_allocation_counters != nullptr) {
212 auto& counters = *active_allocation_counters;
213 ++counters.allocations;
214 counters.allocation_bytes += size;
215 const auto bytes = static_cast<std::uint64_t>(size);
216 const auto maximum = std::numeric_limits<std::uint64_t>::max();
217 counters.live_bytes = bytes > maximum - counters.live_bytes
218 ? maximum
219 : counters.live_bytes + bytes;
220 if (counters.live_bytes > counters.peak_live_bytes) {
221 counters.peak_live_bytes = counters.live_bytes;
222 }
223 }
224}
225
227inline void record_deallocation(std::size_t size = 0) noexcept {
228 if (active_allocation_counters != nullptr) {
229 auto& counters = *active_allocation_counters;
230 ++counters.deallocations;
231 counters.deallocation_bytes += size;
232 const auto bytes = static_cast<std::uint64_t>(size);
233 counters.live_bytes =
234 bytes > counters.live_bytes ? 0 : counters.live_bytes - bytes;
235 }
236}
237
239inline void event_path_clear(std::uint64_t nodes) noexcept {
240 if (active_path_counters != nullptr) {
241 ++active_path_counters->scratch_clear_calls;
242 active_path_counters->scratch_clear_nodes += nodes;
243 }
244}
245
247inline void event_path_initialize() noexcept {
248 if (active_path_counters != nullptr) {
249 ++active_path_counters->initializations;
250 }
251}
252
254inline void event_path_start_passability_check() noexcept {
255 if (active_path_counters != nullptr) {
256 ++active_path_counters->start_passability_checks;
257 }
258}
259
261inline void event_path_goal_passability_check() noexcept {
262 if (active_path_counters != nullptr) {
263 ++active_path_counters->goal_passability_checks;
264 }
265}
266
268inline void event_path_heap_push() noexcept {
269 if (active_path_counters != nullptr) {
270 ++active_path_counters->heap_pushes;
271 }
272}
273
275inline void event_path_heap_pop() noexcept {
276 if (active_path_counters != nullptr) {
277 ++active_path_counters->heap_pops;
278 }
279}
280
282inline void event_path_skip_pop(bool closed) noexcept {
283 if (active_path_counters != nullptr) {
284 if (closed) {
285 ++active_path_counters->closed_pops;
286 } else {
287 ++active_path_counters->stale_pops;
288 }
289 }
290}
291
293inline void event_path_neighbor_candidate() noexcept {
294 if (active_path_counters != nullptr) {
295 ++active_path_counters->neighbor_candidates;
296 }
297}
298
300inline void event_path_passability_check() noexcept {
301 if (active_path_counters != nullptr) {
302 ++active_path_counters->passability_checks;
303 }
304}
305
307inline void event_path_cost_read() noexcept {
308 if (active_path_counters != nullptr) {
309 ++active_path_counters->cost_reads;
310 }
311}
312
314inline void event_path_neighbor_blocked() noexcept {
315 if (active_path_counters != nullptr) {
316 ++active_path_counters->blocked_neighbors;
317 }
318}
319
321inline void event_path_neighbor_closed() noexcept {
322 if (active_path_counters != nullptr) {
323 ++active_path_counters->closed_neighbors;
324 }
325}
326
328inline void event_path_relax_attempt() noexcept {
329 if (active_path_counters != nullptr) {
330 ++active_path_counters->relax_attempts;
331 }
332}
333
335inline void event_path_relax_success() noexcept {
336 if (active_path_counters != nullptr) {
337 ++active_path_counters->relax_successes;
338 }
339}
340
342inline void event_path_touch_node() noexcept {
343 if (active_path_counters != nullptr) {
344 ++active_path_counters->touched_nodes;
345 }
346}
347
349inline void event_path_heuristic() noexcept {
350 if (active_path_counters != nullptr) {
351 ++active_path_counters->heuristic_calls;
352 }
353}
354
356inline void event_path_reconstruct_node() noexcept {
357 if (active_path_counters != nullptr) {
358 ++active_path_counters->reconstructed_nodes;
359 }
360}
361
363inline void event_queued_phase_execute(std::uint64_t operations) noexcept {
364 if (active_queued_phase_counters != nullptr) {
365 ++active_queued_phase_counters->phase_calls;
366 active_queued_phase_counters->phase_operations += operations;
367 }
368}
369
371inline void event_queued_phase_invalid_range() noexcept {
372 if (active_queued_phase_counters != nullptr) {
373 ++active_queued_phase_counters->phase_invalid_ranges;
374 }
375}
376
378inline void event_queued_phase_failure() noexcept {
379 if (active_queued_phase_counters != nullptr) {
380 ++active_queued_phase_counters->phase_failures;
381 }
382}
383
385inline void event_queued_partitioned_phase(std::uint64_t partitions) noexcept {
386 if (active_queued_phase_counters != nullptr) {
387 ++active_queued_phase_counters->partitioned_phase_calls;
388 active_queued_phase_counters->dirty_partitions += partitions;
389 }
390}
391
393inline void event_queued_scoped_thread_dispatch(
394 std::uint64_t workers) noexcept {
395 if (active_queued_phase_counters != nullptr) {
396 ++active_queued_phase_counters->scoped_thread_calls;
397 active_queued_phase_counters->scoped_thread_workers += workers;
398 }
399}
400
402inline void event_queued_worker_pool_dispatch(std::uint64_t workers) noexcept {
403 if (active_queued_phase_counters != nullptr) {
404 ++active_queued_phase_counters->worker_pool_calls;
405 active_queued_phase_counters->worker_pool_workers += workers;
406 }
407}
408
410inline void event_queued_dirty_collect(std::uint64_t records) noexcept {
411 if (active_queued_phase_counters != nullptr) {
412 active_queued_phase_counters->dirty_records_collected += records;
413 }
414}
415
417inline void event_queued_dirty_merge(std::uint64_t chunks) noexcept {
418 if (active_queued_phase_counters != nullptr) {
419 active_queued_phase_counters->dirty_chunks_merged += chunks;
420 }
421}
422#endif
423
438 std::uint64_t offered = 0;
440 std::uint64_t admitted = 0;
442 std::uint64_t rejected = 0;
444 std::uint64_t coalesced_into_pending = 0;
446 std::uint64_t completed = 0;
448 std::uint64_t cancelled = 0;
450 std::uint64_t superseded = 0;
452 std::uint64_t stale = 0;
454 std::uint64_t failed = 0;
456 std::uint64_t dropped_after_admission = 0;
458 std::uint64_t offered_work_units = 0;
460 std::uint64_t consumed_work_units = 0;
462 std::uint64_t outstanding_current = 0;
464 std::uint64_t outstanding_high_water = 0;
466 std::uint64_t inventory_tick_weighted = 0;
471
473 [[nodiscard]] constexpr auto terminal() const noexcept -> std::uint64_t {
474 return completed + cancelled + superseded + stale + failed +
476 }
477
479 [[nodiscard]] constexpr auto admission_identity_holds() const noexcept
480 -> bool {
482 }
483
485 [[nodiscard]] constexpr auto retention_identity_holds() const noexcept
486 -> bool {
488 }
489
491 void reset() noexcept { *this = FlowCounters{}; }
492};
493
509 std::uint64_t last_observed_tick = 0;
510
513 void observe_tick(std::uint64_t tick) noexcept {
514 if (tick > last_observed_tick) {
515 counters.inventory_tick_weighted +=
516 counters.outstanding_current * (tick - last_observed_tick);
517 last_observed_tick = tick;
518 }
519 }
520
522 void record_admitted() noexcept {
523 ++counters.admitted;
524 ++counters.outstanding_current;
525 if (counters.outstanding_current > counters.outstanding_high_water) {
526 counters.outstanding_high_water = counters.outstanding_current;
527 }
528 }
529
531 void record_left_outstanding() noexcept {
532 if (counters.outstanding_current > 0) {
533 --counters.outstanding_current;
534 }
535 }
536};
537
552
554[[nodiscard]] inline auto snapshot(const FlowAccounting& accounting) noexcept
556 return FlowHealthSnapshot{
557 accounting.counters,
558 accounting.counters.admission_identity_holds(),
559 accounting.counters.retention_identity_holds(),
560 };
561}
562
563} // namespace tess::diagnostics
Definition diagnostics.h:103
Definition diagnostics.h:505
void observe_tick(std::uint64_t tick) noexcept
Definition diagnostics.h:513
void record_admitted() noexcept
Records one admission at the current observation tick.
Definition diagnostics.h:522
std::uint64_t last_observed_tick
The most recent tick passed to an observation.
Definition diagnostics.h:509
void record_left_outstanding() noexcept
Records one terminal transition leaving the outstanding set.
Definition diagnostics.h:531
FlowCounters counters
The accumulated flow counters.
Definition diagnostics.h:507
Definition diagnostics.h:436
std::uint64_t offered_work_units
Work units offered through explicitly bounded budgets.
Definition diagnostics.h:458
std::uint64_t failed
Items that terminated in an error state.
Definition diagnostics.h:454
std::uint64_t oldest_outstanding_age_ticks
Age in ticks of the oldest still-outstanding item, as last observed.
Definition diagnostics.h:470
std::uint64_t superseded
Items replaced by a newer admission of the same slot or goal.
Definition diagnostics.h:450
std::uint64_t consumed_work_units
Work units actually consumed by flow items.
Definition diagnostics.h:460
std::uint64_t outstanding_current
Admitted, non-terminal items right now.
Definition diagnostics.h:462
std::uint64_t admitted
Offers accepted as new flow items.
Definition diagnostics.h:440
std::uint64_t cancelled
Items explicitly cancelled by the caller.
Definition diagnostics.h:448
constexpr auto retention_identity_holds() const noexcept -> bool
Every admitted item is terminal or still outstanding — never both.
Definition diagnostics.h:485
std::uint64_t residence_ticks_accumulated
Total admission-to-terminal ticks over terminalized items.
Definition diagnostics.h:468
constexpr auto terminal() const noexcept -> std::uint64_t
Sum of every terminal outcome bucket.
Definition diagnostics.h:473
std::uint64_t outstanding_high_water
Highest simultaneous outstanding count observed.
Definition diagnostics.h:464
std::uint64_t stale
Items whose result no longer satisfies its version requirement.
Definition diagnostics.h:452
std::uint64_t rejected
Offers refused at the admission boundary.
Definition diagnostics.h:442
std::uint64_t coalesced_into_pending
Offers absorbed by an item that was already pending.
Definition diagnostics.h:444
void reset() noexcept
Returns the counters to their zero-initialized state.
Definition diagnostics.h:491
std::uint64_t dropped_after_admission
Admitted items discarded before reaching any other terminal state.
Definition diagnostics.h:456
std::uint64_t inventory_tick_weighted
Sum over observed ticks of outstanding items times elapsed ticks.
Definition diagnostics.h:466
constexpr auto admission_identity_holds() const noexcept -> bool
Every offer was admitted, rejected, or coalesced — never lost.
Definition diagnostics.h:479
std::uint64_t completed
Items that reached their intended result.
Definition diagnostics.h:446
std::uint64_t offered
Admission offers, including rejected and coalesced ones.
Definition diagnostics.h:438
Definition diagnostics.h:544
bool retention_identity_ok
Whether every admitted item is terminal or outstanding.
Definition diagnostics.h:550
FlowCounters counters
The counters at snapshot time.
Definition diagnostics.h:546
bool admission_identity_ok
Whether every offer is accounted for.
Definition diagnostics.h:548
Definition diagnostics.h:78
Definition diagnostics.h:118