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