Skip to main content

solverforge_solver/stats/
telemetry.rs

1/* Solver statistics (zero-erasure).
2
3Stack-allocated statistics for solver and phase performance tracking.
4*/
5
6use std::time::Duration;
7
8/* Solver-level statistics.
9
10Tracks aggregate metrics across all phases of a solve run.
11
12# Example
13
14```
15use solverforge_solver::stats::SolverStats;
16use std::time::Duration;
17
18let mut stats = SolverStats::default();
19stats.start();
20stats.record_step();
21stats.record_generated_move(Duration::from_millis(1));
22stats.record_evaluated_move(Duration::from_millis(2));
23stats.record_move_accepted();
24stats.record_generated_move(Duration::from_millis(1));
25stats.record_evaluated_move(Duration::from_millis(2));
26
27assert_eq!(stats.step_count, 1);
28assert_eq!(stats.moves_evaluated, 2);
29assert_eq!(stats.moves_accepted, 1);
30```
31*/
32#[derive(Debug, Clone, Default, PartialEq)]
33pub struct SelectorTelemetry {
34    pub selector_index: usize,
35    pub selector_label: String,
36    pub moves_generated: u64,
37    pub moves_evaluated: u64,
38    pub moves_accepted: u64,
39    pub moves_applied: u64,
40    pub moves_not_doable: u64,
41    pub moves_acceptor_rejected: u64,
42    pub moves_forager_ignored: u64,
43    pub moves_hard_improving: u64,
44    pub moves_hard_neutral: u64,
45    pub moves_hard_worse: u64,
46    pub conflict_repair_provider_generated: u64,
47    pub conflict_repair_duplicate_filtered: u64,
48    pub conflict_repair_illegal_filtered: u64,
49    pub conflict_repair_not_doable_filtered: u64,
50    pub conflict_repair_hard_improving: u64,
51    pub conflict_repair_exposed: u64,
52    pub generation_time: Duration,
53    pub evaluation_time: Duration,
54}
55
56#[derive(Debug, Clone, Default, PartialEq)]
57pub struct SolverTelemetry {
58    pub elapsed: Duration,
59    pub step_count: u64,
60    pub moves_generated: u64,
61    pub moves_evaluated: u64,
62    pub moves_accepted: u64,
63    pub moves_applied: u64,
64    pub moves_not_doable: u64,
65    pub moves_acceptor_rejected: u64,
66    pub moves_forager_ignored: u64,
67    pub moves_hard_improving: u64,
68    pub moves_hard_neutral: u64,
69    pub moves_hard_worse: u64,
70    pub conflict_repair_provider_generated: u64,
71    pub conflict_repair_duplicate_filtered: u64,
72    pub conflict_repair_illegal_filtered: u64,
73    pub conflict_repair_not_doable_filtered: u64,
74    pub conflict_repair_hard_improving: u64,
75    pub conflict_repair_exposed: u64,
76    pub score_calculations: u64,
77    pub construction_slots_assigned: u64,
78    pub construction_slots_kept: u64,
79    pub construction_slots_no_doable: u64,
80    pub coverage_required_remaining: u64,
81    pub generation_time: Duration,
82    pub evaluation_time: Duration,
83    pub selector_telemetry: Vec<SelectorTelemetry>,
84}
85
86impl SolverTelemetry {
87    pub const fn new_const() -> Self {
88        Self {
89            elapsed: Duration::ZERO,
90            step_count: 0,
91            moves_generated: 0,
92            moves_evaluated: 0,
93            moves_accepted: 0,
94            moves_applied: 0,
95            moves_not_doable: 0,
96            moves_acceptor_rejected: 0,
97            moves_forager_ignored: 0,
98            moves_hard_improving: 0,
99            moves_hard_neutral: 0,
100            moves_hard_worse: 0,
101            conflict_repair_provider_generated: 0,
102            conflict_repair_duplicate_filtered: 0,
103            conflict_repair_illegal_filtered: 0,
104            conflict_repair_not_doable_filtered: 0,
105            conflict_repair_hard_improving: 0,
106            conflict_repair_exposed: 0,
107            score_calculations: 0,
108            construction_slots_assigned: 0,
109            construction_slots_kept: 0,
110            construction_slots_no_doable: 0,
111            coverage_required_remaining: 0,
112            generation_time: Duration::ZERO,
113            evaluation_time: Duration::ZERO,
114            selector_telemetry: Vec::new(),
115        }
116    }
117}
118
119#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
120pub struct Throughput {
121    pub count: u64,
122    pub elapsed: Duration,
123}
124
125pub(crate) fn whole_units_per_second(count: u64, elapsed: Duration) -> u128 {
126    let nanos = elapsed.as_nanos();
127    if nanos == 0 {
128        0
129    } else {
130        u128::from(count)
131            .saturating_mul(1_000_000_000)
132            .checked_div(nanos)
133            .unwrap_or(0)
134    }
135}
136
137pub(crate) fn format_duration(duration: Duration) -> String {
138    let secs = duration.as_secs();
139    let nanos = duration.subsec_nanos();
140
141    if secs >= 60 {
142        let mins = secs / 60;
143        let rem_secs = secs % 60;
144        return format!("{mins}m {rem_secs}s");
145    }
146
147    if secs > 0 {
148        let millis = nanos / 1_000_000;
149        if millis == 0 {
150            return format!("{secs}s");
151        }
152        return format!("{secs}s {millis}ms");
153    }
154
155    let millis = nanos / 1_000_000;
156    if millis > 0 {
157        return format!("{millis}ms");
158    }
159
160    let micros = nanos / 1_000;
161    if micros > 0 {
162        return format!("{micros}us");
163    }
164
165    format!("{nanos}ns")
166}