solverforge_solver/stats/
phase.rs1use std::time::{Duration, Instant};
2
3use super::{SelectorTelemetry, Throughput};
4
5#[derive(Debug)]
6pub struct PhaseStats {
7 pub phase_index: usize,
9 pub phase_type: &'static str,
11 start_time: Instant,
12 pub step_count: u64,
14 pub moves_generated: u64,
16 pub moves_evaluated: u64,
18 pub moves_accepted: u64,
20 pub moves_applied: u64,
22 pub moves_not_doable: u64,
23 pub moves_acceptor_rejected: u64,
24 pub moves_forager_ignored: u64,
25 pub moves_hard_improving: u64,
26 pub moves_hard_neutral: u64,
27 pub moves_hard_worse: u64,
28 pub conflict_repair_provider_generated: u64,
29 pub conflict_repair_duplicate_filtered: u64,
30 pub conflict_repair_illegal_filtered: u64,
31 pub conflict_repair_not_doable_filtered: u64,
32 pub conflict_repair_hard_improving: u64,
33 pub conflict_repair_exposed: u64,
34 pub score_calculations: u64,
36 pub construction_slots_assigned: u64,
37 pub construction_slots_kept: u64,
38 pub construction_slots_no_doable: u64,
39 pub coverage_required_remaining: u64,
40 generation_time: Duration,
41 evaluation_time: Duration,
42 selector_stats: Vec<SelectorTelemetry>,
43}
44
45impl PhaseStats {
46 pub fn new(phase_index: usize, phase_type: &'static str) -> Self {
48 Self {
49 phase_index,
50 phase_type,
51 start_time: Instant::now(),
52 step_count: 0,
53 moves_generated: 0,
54 moves_evaluated: 0,
55 moves_accepted: 0,
56 moves_applied: 0,
57 moves_not_doable: 0,
58 moves_acceptor_rejected: 0,
59 moves_forager_ignored: 0,
60 moves_hard_improving: 0,
61 moves_hard_neutral: 0,
62 moves_hard_worse: 0,
63 conflict_repair_provider_generated: 0,
64 conflict_repair_duplicate_filtered: 0,
65 conflict_repair_illegal_filtered: 0,
66 conflict_repair_not_doable_filtered: 0,
67 conflict_repair_hard_improving: 0,
68 conflict_repair_exposed: 0,
69 score_calculations: 0,
70 construction_slots_assigned: 0,
71 construction_slots_kept: 0,
72 construction_slots_no_doable: 0,
73 coverage_required_remaining: 0,
74 generation_time: Duration::default(),
75 evaluation_time: Duration::default(),
76 selector_stats: Vec::new(),
77 }
78 }
79
80 pub fn elapsed(&self) -> Duration {
81 self.start_time.elapsed()
82 }
83
84 pub fn record_step(&mut self) {
86 self.step_count += 1;
87 }
88
89 pub fn record_generated_batch(&mut self, count: u64, duration: Duration) {
91 self.moves_generated += count;
92 self.generation_time += duration;
93 }
94
95 pub fn record_selector_generated(
96 &mut self,
97 selector_index: usize,
98 count: u64,
99 duration: Duration,
100 ) {
101 self.record_generated_batch(count, duration);
102 let selector = self.selector_stats_entry(selector_index);
103 selector.moves_generated += count;
104 selector.generation_time += duration;
105 }
106
107 pub fn record_generation_time(&mut self, duration: Duration) {
109 self.generation_time += duration;
110 }
111
112 pub fn record_generated_move(&mut self, duration: Duration) {
114 self.record_generated_batch(1, duration);
115 }
116
117 pub fn record_evaluated_move(&mut self, duration: Duration) {
119 self.moves_evaluated += 1;
120 self.evaluation_time += duration;
121 }
122
123 pub fn record_selector_evaluated(&mut self, selector_index: usize, duration: Duration) {
124 self.record_evaluated_move(duration);
125 let selector = self.selector_stats_entry(selector_index);
126 selector.moves_evaluated += 1;
127 selector.evaluation_time += duration;
128 }
129
130 pub fn record_move_accepted(&mut self) {
132 self.moves_accepted += 1;
133 }
134
135 pub fn record_selector_accepted(&mut self, selector_index: usize) {
136 self.record_move_accepted();
137 self.selector_stats_entry(selector_index).moves_accepted += 1;
138 }
139
140 pub fn record_move_applied(&mut self) {
141 self.moves_applied += 1;
142 }
143
144 pub fn record_selector_applied(&mut self, selector_index: usize) {
145 self.record_move_applied();
146 self.selector_stats_entry(selector_index).moves_applied += 1;
147 }
148
149 pub fn record_move_not_doable(&mut self) {
150 self.moves_not_doable += 1;
151 }
152
153 pub fn record_selector_not_doable(&mut self, selector_index: usize) {
154 self.record_move_not_doable();
155 self.selector_stats_entry(selector_index).moves_not_doable += 1;
156 }
157
158 pub fn record_move_acceptor_rejected(&mut self) {
159 self.moves_acceptor_rejected += 1;
160 }
161
162 pub fn record_selector_acceptor_rejected(&mut self, selector_index: usize) {
163 self.record_move_acceptor_rejected();
164 self.selector_stats_entry(selector_index)
165 .moves_acceptor_rejected += 1;
166 }
167
168 pub fn record_moves_forager_ignored(&mut self, count: u64) {
169 self.moves_forager_ignored += count;
170 }
171
172 pub fn record_move_hard_improving(&mut self) {
173 self.moves_hard_improving += 1;
174 }
175
176 pub fn record_move_hard_neutral(&mut self) {
177 self.moves_hard_neutral += 1;
178 }
179
180 pub fn record_move_hard_worse(&mut self) {
181 self.moves_hard_worse += 1;
182 }
183
184 pub fn record_conflict_repair_provider_generated(&mut self, count: u64) {
185 self.conflict_repair_provider_generated += count;
186 }
187
188 pub fn record_conflict_repair_duplicate_filtered(&mut self) {
189 self.conflict_repair_duplicate_filtered += 1;
190 }
191
192 pub fn record_conflict_repair_illegal_filtered(&mut self) {
193 self.conflict_repair_illegal_filtered += 1;
194 }
195
196 pub fn record_conflict_repair_not_doable_filtered(&mut self) {
197 self.conflict_repair_not_doable_filtered += 1;
198 }
199
200 pub fn record_conflict_repair_hard_improving(&mut self) {
201 self.conflict_repair_hard_improving += 1;
202 }
203
204 pub fn record_conflict_repair_exposed(&mut self) {
205 self.conflict_repair_exposed += 1;
206 }
207
208 pub fn record_score_calculation(&mut self) {
210 self.score_calculations += 1;
211 }
212
213 pub fn record_construction_slot_assigned(&mut self) {
214 self.construction_slots_assigned += 1;
215 }
216
217 pub fn record_construction_slot_kept(&mut self) {
218 self.construction_slots_kept += 1;
219 }
220
221 pub fn record_construction_slot_no_doable(&mut self) {
222 self.construction_slots_no_doable += 1;
223 }
224
225 pub fn record_coverage_required_remaining(&mut self, count: u64) {
226 self.coverage_required_remaining = count;
227 }
228
229 pub fn generated_throughput(&self) -> Throughput {
230 Throughput {
231 count: self.moves_generated,
232 elapsed: self.generation_time,
233 }
234 }
235
236 pub fn evaluated_throughput(&self) -> Throughput {
237 Throughput {
238 count: self.moves_evaluated,
239 elapsed: self.evaluation_time,
240 }
241 }
242
243 pub fn acceptance_rate(&self) -> f64 {
244 if self.moves_evaluated == 0 {
245 0.0
246 } else {
247 self.moves_accepted as f64 / self.moves_evaluated as f64
248 }
249 }
250
251 pub fn generation_time(&self) -> Duration {
252 self.generation_time
253 }
254
255 pub fn evaluation_time(&self) -> Duration {
256 self.evaluation_time
257 }
258
259 pub fn selector_telemetry(&self) -> &[SelectorTelemetry] {
260 &self.selector_stats
261 }
262
263 pub fn record_selector_generated_with_label(
264 &mut self,
265 selector_index: usize,
266 selector_label: impl Into<String>,
267 count: u64,
268 duration: Duration,
269 ) {
270 self.record_generated_batch(count, duration);
271 let selector = self.selector_stats_entry_with_label(selector_index, selector_label);
272 selector.moves_generated += count;
273 selector.generation_time += duration;
274 }
275
276 fn selector_stats_entry(&mut self, selector_index: usize) -> &mut SelectorTelemetry {
277 self.selector_stats_entry_with_label(selector_index, format!("selector-{selector_index}"))
278 }
279
280 fn selector_stats_entry_with_label(
281 &mut self,
282 selector_index: usize,
283 selector_label: impl Into<String>,
284 ) -> &mut SelectorTelemetry {
285 let selector_label = selector_label.into();
286 if let Some(position) = self
287 .selector_stats
288 .iter()
289 .position(|entry| entry.selector_index == selector_index)
290 {
291 if self.selector_stats[position]
292 .selector_label
293 .starts_with("selector-")
294 && !selector_label.starts_with("selector-")
295 {
296 self.selector_stats[position].selector_label = selector_label;
297 }
298 return &mut self.selector_stats[position];
299 }
300 self.selector_stats.push(SelectorTelemetry {
301 selector_index,
302 selector_label,
303 ..SelectorTelemetry::default()
304 });
305 self.selector_stats
306 .last_mut()
307 .expect("selector stats entry was just inserted")
308 }
309}