solverforge_solver/stats/
telemetry.rs1use std::time::Duration;
7
8use super::CandidateTraceTelemetry;
9
10#[derive(Debug, Clone, Default, PartialEq)]
38pub struct SelectorTelemetry {
39 pub selector_index: usize,
40 pub selector_label: String,
41 pub moves_generated: u64,
44 pub moves_evaluated: u64,
45 pub moves_accepted: u64,
46 pub moves_applied: u64,
47 pub moves_not_doable: u64,
48 pub moves_acceptor_rejected: u64,
49 pub moves_forager_ignored: u64,
50 pub moves_hard_improving: u64,
51 pub moves_hard_neutral: u64,
52 pub moves_hard_worse: u64,
53 pub conflict_repair_provider_generated: u64,
54 pub conflict_repair_duplicate_filtered: u64,
55 pub conflict_repair_illegal_filtered: u64,
56 pub conflict_repair_not_doable_filtered: u64,
57 pub conflict_repair_hard_improving: u64,
58 pub conflict_repair_exposed: u64,
59 pub generation_time: Duration,
60 pub evaluation_time: Duration,
61}
62
63#[derive(Debug, Clone, Default, PartialEq)]
64pub struct MoveTelemetry {
65 pub move_label: String,
66 pub moves_generated: u64,
69 pub moves_evaluated: u64,
70 pub moves_accepted: u64,
71 pub moves_applied: u64,
72 pub moves_not_doable: u64,
73 pub moves_acceptor_rejected: u64,
74 pub moves_forager_ignored: u64,
75 pub moves_score_improving: u64,
76 pub moves_applied_improving: u64,
77 pub moves_score_equal: u64,
78 pub moves_score_worse: u64,
79 pub moves_rejected_improving: u64,
80 pub applied_score_improvement: f64,
81}
82
83#[derive(Debug, Clone, Default, PartialEq)]
84pub struct PhaseTelemetry {
85 pub phase_index: usize,
87 pub phase_type: String,
88 pub elapsed: Duration,
89 pub step_count: u64,
90 pub moves_generated: u64,
93 pub moves_evaluated: u64,
94 pub moves_speculative: u64,
97 pub moves_accepted: u64,
98 pub moves_applied: u64,
99 pub moves_score_improving: u64,
100 pub moves_applied_improving: u64,
101 pub score_calculations: u64,
102 pub generation_time: Duration,
103 pub evaluation_time: Duration,
104}
105
106#[derive(Debug, Clone, Copy, Default, PartialEq)]
107pub struct AppliedMoveTelemetry {
108 pub step_index: u64,
109 pub move_label: &'static str,
110 pub selected_candidate_index: usize,
111 pub moves_generated_this_step: u64,
112 pub moves_evaluated_this_step: u64,
113 pub moves_accepted_this_step: u64,
114 pub moves_forager_ignored_this_step: u64,
115 pub score_before: f64,
116 pub score_after: f64,
117 pub score_delta: f64,
118 pub hard_feasible_before: bool,
119 pub hard_feasible_after: bool,
120}
121
122#[derive(Debug, Clone, Default, PartialEq)]
123pub struct SolverTelemetry {
124 pub elapsed: Duration,
125 pub step_count: u64,
126 pub moves_generated: u64,
129 pub moves_evaluated: u64,
130 pub moves_speculative: u64,
133 pub moves_accepted: u64,
134 pub moves_applied: u64,
135 pub moves_score_improving: u64,
136 pub moves_applied_improving: u64,
137 pub moves_not_doable: u64,
138 pub moves_acceptor_rejected: u64,
139 pub moves_forager_ignored: u64,
140 pub moves_hard_improving: u64,
141 pub moves_hard_neutral: u64,
142 pub moves_hard_worse: u64,
143 pub conflict_repair_provider_generated: u64,
144 pub conflict_repair_duplicate_filtered: u64,
145 pub conflict_repair_illegal_filtered: u64,
146 pub conflict_repair_not_doable_filtered: u64,
147 pub conflict_repair_hard_improving: u64,
148 pub conflict_repair_exposed: u64,
149 pub score_calculations: u64,
150 pub construction_slots_assigned: u64,
151 pub construction_slots_kept: u64,
152 pub construction_slots_no_doable: u64,
153 pub scalar_assignment_required_remaining: u64,
154 pub generation_time: Duration,
155 pub evaluation_time: Duration,
156 pub phases: Vec<PhaseTelemetry>,
161 pub selector_telemetry: Vec<SelectorTelemetry>,
162 pub move_telemetry: Vec<MoveTelemetry>,
163 pub applied_move_trace: Vec<AppliedMoveTelemetry>,
164 pub candidate_trace: Option<CandidateTraceTelemetry>,
167}
168
169impl SolverTelemetry {
170 pub const fn new_const() -> Self {
171 Self {
172 elapsed: Duration::ZERO,
173 step_count: 0,
174 moves_generated: 0,
175 moves_evaluated: 0,
176 moves_speculative: 0,
177 moves_accepted: 0,
178 moves_applied: 0,
179 moves_score_improving: 0,
180 moves_applied_improving: 0,
181 moves_not_doable: 0,
182 moves_acceptor_rejected: 0,
183 moves_forager_ignored: 0,
184 moves_hard_improving: 0,
185 moves_hard_neutral: 0,
186 moves_hard_worse: 0,
187 conflict_repair_provider_generated: 0,
188 conflict_repair_duplicate_filtered: 0,
189 conflict_repair_illegal_filtered: 0,
190 conflict_repair_not_doable_filtered: 0,
191 conflict_repair_hard_improving: 0,
192 conflict_repair_exposed: 0,
193 score_calculations: 0,
194 construction_slots_assigned: 0,
195 construction_slots_kept: 0,
196 construction_slots_no_doable: 0,
197 scalar_assignment_required_remaining: 0,
198 generation_time: Duration::ZERO,
199 evaluation_time: Duration::ZERO,
200 phases: Vec::new(),
201 selector_telemetry: Vec::new(),
202 move_telemetry: Vec::new(),
203 applied_move_trace: Vec::new(),
204 candidate_trace: None,
205 }
206 }
207
208 pub(crate) fn take_candidate_trace(&mut self) -> Option<CandidateTraceTelemetry> {
217 self.candidate_trace.take()
218 }
219
220 pub(crate) fn split_candidate_trace(mut self) -> (Self, Option<CandidateTraceTelemetry>) {
223 let candidate_trace = self.take_candidate_trace();
224 (self, candidate_trace)
225 }
226}
227
228#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
229pub struct Throughput {
230 pub count: u64,
231 pub elapsed: Duration,
232}
233
234pub(crate) fn whole_units_per_second(count: u64, elapsed: Duration) -> u128 {
235 let nanos = elapsed.as_nanos();
236 if nanos == 0 {
237 0
238 } else {
239 u128::from(count)
240 .saturating_mul(1_000_000_000)
241 .checked_div(nanos)
242 .unwrap_or(0)
243 }
244}
245
246pub(crate) fn format_duration(duration: Duration) -> String {
247 let secs = duration.as_secs();
248 let nanos = duration.subsec_nanos();
249
250 if secs >= 60 {
251 let mins = secs / 60;
252 let rem_secs = secs % 60;
253 return format!("{mins}m {rem_secs}s");
254 }
255
256 if secs > 0 {
257 let millis = nanos / 1_000_000;
258 if millis == 0 {
259 return format!("{secs}s");
260 }
261 return format!("{secs}s {millis}ms");
262 }
263
264 let millis = nanos / 1_000_000;
265 if millis > 0 {
266 return format!("{millis}ms");
267 }
268
269 let micros = nanos / 1_000;
270 if micros > 0 {
271 return format!("{micros}us");
272 }
273
274 format!("{nanos}ns")
275}