hashiverse_lib/tools/
pow_required_estimator.rs1use crate::tools::time::{DurationMillis, TimeMillis, MILLIS_IN_MILLISECOND};
19use crate::tools::types::Pow;
20
21pub struct PowRequiredEstimator {
34 description: String,
35 pow_required: Pow,
36 total_iterations: usize,
37 best_pow_so_far: Pow,
38 started_at_millis: TimeMillis,
39}
40
41impl PowRequiredEstimator {
42 pub fn new(started_at_millis: TimeMillis, description: &str, pow_required: Pow) -> Self {
43 Self {
44 description: description.to_string(),
45 pow_required,
46 total_iterations: 0,
47 best_pow_so_far: Pow(0),
48 started_at_millis,
49 }
50 }
51
52 fn report_large_number(num: usize) -> String {
53 let num = num as f64;
54
55 if num > 1e9 {
56 return format!("{:.2}B", num / 1e9);
57 }
58 if num > 1e6 {
59 return format!("{:.2}M", num / 1e6);
60 }
61 if num > 1e3 {
62 return format!("{:.2}k", num / 1e3);
63 }
64
65 format!("{}", num)
66 }
67
68 pub fn record_batch_and_estimate(&mut self, current_time_millis: TimeMillis, iterations_in_batch: usize, best_pow_in_batch: Pow) -> String {
70 self.total_iterations += iterations_in_batch;
71 if best_pow_in_batch > self.best_pow_so_far {
72 self.best_pow_so_far = best_pow_in_batch;
73 }
74
75 let elapsed_duration_millis = current_time_millis - self.started_at_millis;
76
77 if elapsed_duration_millis < MILLIS_IN_MILLISECOND || self.total_iterations == 0 {
78 return format!(
79 "{}: PoW {}/{} bits | {} | {} iters | too early to estimate",
80 self.description,
81 self.best_pow_so_far.0,
82 self.pow_required.0,
83 elapsed_duration_millis,
84 Self::report_large_number(self.total_iterations)
85 );
86 }
87
88 let iterations_per_second = 1000.0 * self.total_iterations as f64 / elapsed_duration_millis.0 as f64;
89 let expected_total_iterations = (2.0f64).powi(self.pow_required.0 as i32);
91 let expected_total_duration = DurationMillis((1000.0 * expected_total_iterations / iterations_per_second) as i64);
92 let eta_remaining_millis = expected_total_duration - elapsed_duration_millis;
93 let eta_one_sigma = expected_total_duration;
95 let progress_pct = self.total_iterations as f64 / expected_total_iterations * 100.0;
96
97 format!(
98 "{}: PoW {}/{} bits | {} | {} iters | {}/s | {:.1}% of expected | ETA ~{} \u{00b1}{}",
99 self.description,
100 self.best_pow_so_far.0,
101 self.pow_required.0,
102 elapsed_duration_millis,
103 Self::report_large_number(self.total_iterations),
104 Self::report_large_number(iterations_per_second as usize),
105 progress_pct,
106 eta_remaining_millis,
107 eta_one_sigma,
108 )
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 fn make_estimator() -> PowRequiredEstimator {
117 PowRequiredEstimator::new(TimeMillis(0), "test", Pow(24))
118 }
119
120 #[test]
121 fn record_batch_accumulates_iterations_and_tracks_best_pow() {
122 let mut estimator = make_estimator();
123
124 estimator.record_batch_and_estimate(TimeMillis(100), 1024, Pow(10));
125 assert_eq!(estimator.total_iterations, 1024);
126 assert_eq!(estimator.best_pow_so_far, Pow(10));
127
128 estimator.record_batch_and_estimate(TimeMillis(200), 1024, Pow(8)); assert_eq!(estimator.total_iterations, 2048);
130 assert_eq!(estimator.best_pow_so_far, Pow(10));
131
132 estimator.record_batch_and_estimate(TimeMillis(300), 1024, Pow(15)); assert_eq!(estimator.total_iterations, 3072);
134 assert_eq!(estimator.best_pow_so_far, Pow(15));
135 }
136
137 #[test]
138 fn progress_string_contains_key_fields() {
139 let mut estimator = make_estimator();
140 let output = estimator.record_batch_and_estimate(TimeMillis(1000), 65536, Pow(18));
141
142 assert!(output.contains("test"), "should include description: {}", output);
143 assert!(output.contains("18/24"), "should show best/required bits: {}", output);
144 assert!(output.contains("1s"), "should show elapsed time: {}", output);
145 assert!(output.contains("65.54"), "should show iteration count: {}", output);
146 assert!(output.contains("ETA"), "should show ETA: {}", output);
147 }
148
149 #[test]
150 fn progress_string_before_any_elapsed_time() {
151 let mut estimator = make_estimator();
152 let output = estimator.record_batch_and_estimate(TimeMillis(0), 1024, Pow(5));
153 assert!(output.contains("too early"), "{}", output);
154 }
155}