solverforge_solver/stats/
telemetry.rs1use std::time::Duration;
7
8#[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}