Skip to main content

murk_engine/
metrics.rs

1//! Per-tick performance metrics for the simulation engine.
2//!
3//! [`StepMetrics`] captures timing and memory data for a single tick,
4//! enabling telemetry, profiling, and adaptive backoff decisions.
5
6/// Timing and memory metrics collected during a single tick.
7///
8/// All durations are in microseconds. The engine populates these fields
9/// after each `step()` call; consumers (telemetry, backoff logic) read
10/// them from the most recent tick.
11#[derive(Clone, Debug, Default)]
12pub struct StepMetrics {
13    /// Wall-clock time for the entire tick, in microseconds.
14    pub total_us: u64,
15    /// Time spent processing the ingress command queue, in microseconds.
16    pub command_processing_us: u64,
17    /// Per-propagator execution times: `(name, microseconds)`.
18    pub propagator_us: Vec<(String, u64)>,
19    /// Time spent publishing the snapshot to the ring buffer, in microseconds.
20    pub snapshot_publish_us: u64,
21    /// Memory usage of the arena after the tick, in bytes.
22    pub memory_bytes: usize,
23    /// Number of sparse segment ranges available for reuse.
24    pub sparse_retired_ranges: u32,
25    /// Number of sparse segment ranges pending promotion (freed this tick).
26    pub sparse_pending_retired: u32,
27    /// Number of sparse alloc() calls that reused a retired range this tick.
28    pub sparse_reuse_hits: u32,
29    /// Number of sparse alloc() calls that fell through to bump allocation this tick.
30    pub sparse_reuse_misses: u32,
31    /// Cumulative number of ingress rejections due to full queue.
32    pub queue_full_rejections: u64,
33    /// Cumulative number of ingress rejections due to tick-disabled state.
34    pub tick_disabled_rejections: u64,
35    /// Cumulative number of rollback events.
36    pub rollback_events: u64,
37    /// Cumulative number of transitions into tick-disabled state.
38    pub tick_disabled_transitions: u64,
39    /// Cumulative number of worker stall force-unpin events.
40    pub worker_stall_events: u64,
41    /// Cumulative number of ring "not available" events.
42    pub ring_not_available_events: u64,
43    /// Cumulative number of snapshot evictions due to ring overwrite.
44    pub ring_eviction_events: u64,
45    /// Cumulative number of stale/not-yet-written position reads.
46    pub ring_stale_read_events: u64,
47    /// Cumulative number of reader retries caused by overwrite skew.
48    pub ring_skew_retry_events: u64,
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn default_metrics_are_zero() {
57        let m = StepMetrics::default();
58        assert_eq!(m.total_us, 0);
59        assert_eq!(m.command_processing_us, 0);
60        assert!(m.propagator_us.is_empty());
61        assert_eq!(m.snapshot_publish_us, 0);
62        assert_eq!(m.memory_bytes, 0);
63        assert_eq!(m.sparse_retired_ranges, 0);
64        assert_eq!(m.sparse_pending_retired, 0);
65        assert_eq!(m.sparse_reuse_hits, 0);
66        assert_eq!(m.sparse_reuse_misses, 0);
67        assert_eq!(m.queue_full_rejections, 0);
68        assert_eq!(m.tick_disabled_rejections, 0);
69        assert_eq!(m.rollback_events, 0);
70        assert_eq!(m.tick_disabled_transitions, 0);
71        assert_eq!(m.worker_stall_events, 0);
72        assert_eq!(m.ring_not_available_events, 0);
73        assert_eq!(m.ring_eviction_events, 0);
74        assert_eq!(m.ring_stale_read_events, 0);
75        assert_eq!(m.ring_skew_retry_events, 0);
76    }
77
78    #[test]
79    fn metrics_fields_accessible() {
80        let m = StepMetrics {
81            total_us: 100,
82            command_processing_us: 20,
83            propagator_us: vec![("diffusion".to_string(), 50), ("decay".to_string(), 30)],
84            snapshot_publish_us: 10,
85            memory_bytes: 4096,
86            sparse_retired_ranges: 3,
87            sparse_pending_retired: 1,
88            sparse_reuse_hits: 5,
89            sparse_reuse_misses: 2,
90            queue_full_rejections: 11,
91            tick_disabled_rejections: 4,
92            rollback_events: 2,
93            tick_disabled_transitions: 1,
94            worker_stall_events: 3,
95            ring_not_available_events: 7,
96            ring_eviction_events: 9,
97            ring_stale_read_events: 4,
98            ring_skew_retry_events: 2,
99        };
100        assert_eq!(m.total_us, 100);
101        assert_eq!(m.command_processing_us, 20);
102        assert_eq!(m.propagator_us.len(), 2);
103        assert_eq!(m.propagator_us[0].0, "diffusion");
104        assert_eq!(m.propagator_us[0].1, 50);
105        assert_eq!(m.snapshot_publish_us, 10);
106        assert_eq!(m.memory_bytes, 4096);
107        assert_eq!(m.sparse_retired_ranges, 3);
108        assert_eq!(m.sparse_pending_retired, 1);
109        assert_eq!(m.sparse_reuse_hits, 5);
110        assert_eq!(m.sparse_reuse_misses, 2);
111        assert_eq!(m.queue_full_rejections, 11);
112        assert_eq!(m.tick_disabled_rejections, 4);
113        assert_eq!(m.rollback_events, 2);
114        assert_eq!(m.tick_disabled_transitions, 1);
115        assert_eq!(m.worker_stall_events, 3);
116        assert_eq!(m.ring_not_available_events, 7);
117        assert_eq!(m.ring_eviction_events, 9);
118        assert_eq!(m.ring_stale_read_events, 4);
119        assert_eq!(m.ring_skew_retry_events, 2);
120    }
121}