oar_ocr/pipeline/
stats.rs1use std::fmt;
8use std::sync::Mutex;
9
10#[derive(Debug, Clone)]
15pub struct PipelineStats {
16 pub total_processed: usize,
18 pub successful_predictions: usize,
20 pub failed_predictions: usize,
22 pub average_inference_time_ms: f64,
24}
25
26impl PipelineStats {
27 pub fn new() -> Self {
29 Self {
30 total_processed: 0,
31 successful_predictions: 0,
32 failed_predictions: 0,
33 average_inference_time_ms: 0.0,
34 }
35 }
36
37 pub fn success_rate(&self) -> f64 {
39 if self.total_processed == 0 {
40 0.0
41 } else {
42 (self.successful_predictions as f64 / self.total_processed as f64) * 100.0
43 }
44 }
45
46 pub fn failure_rate(&self) -> f64 {
48 if self.total_processed == 0 {
49 0.0
50 } else {
51 (self.failed_predictions as f64 / self.total_processed as f64) * 100.0
52 }
53 }
54
55 pub fn images_per_second(&self) -> f64 {
57 if self.average_inference_time_ms == 0.0 {
58 0.0
59 } else {
60 1000.0 / self.average_inference_time_ms
61 }
62 }
63}
64
65impl Default for PipelineStats {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl fmt::Display for PipelineStats {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 writeln!(f, "Pipeline Statistics:")?;
74 writeln!(f, " Total processed: {}", self.total_processed)?;
75 writeln!(
76 f,
77 " Successful: {} ({:.1}%)",
78 self.successful_predictions,
79 self.success_rate()
80 )?;
81 writeln!(
82 f,
83 " Failed: {} ({:.1}%)",
84 self.failed_predictions,
85 self.failure_rate()
86 )?;
87 writeln!(
88 f,
89 " Average inference time: {:.2} ms",
90 self.average_inference_time_ms
91 )?;
92 writeln!(
93 f,
94 " Processing speed: {:.2} images/sec",
95 self.images_per_second()
96 )?;
97 Ok(())
98 }
99}
100
101#[derive(Debug, Default)]
103pub struct StatsManager {
104 stats: Mutex<PipelineStats>,
106}
107
108impl StatsManager {
109 pub fn new() -> Self {
111 Self::default()
112 }
113
114 pub fn get_stats(&self) -> PipelineStats {
116 self.stats.lock().unwrap().clone()
117 }
118
119 pub fn update_stats(
121 &self,
122 processed_count: usize,
123 successful_count: usize,
124 failed_count: usize,
125 inference_time_ms: f64,
126 ) {
127 let mut stats = self.stats.lock().unwrap();
128
129 let previous_total = stats.total_processed;
130 let previous_average = stats.average_inference_time_ms;
131 let new_total = previous_total + processed_count;
132
133 stats.total_processed = new_total;
134 stats.successful_predictions += successful_count;
135 stats.failed_predictions += failed_count;
136
137 if new_total > 0 {
138 let accumulated_time = previous_average * previous_total as f64;
139 let new_total_time = accumulated_time + inference_time_ms;
140 stats.average_inference_time_ms = new_total_time / new_total as f64;
141 } else {
142 stats.average_inference_time_ms = 0.0;
143 }
144 }
145
146 pub fn reset_stats(&self) {
148 let mut stats = self.stats.lock().unwrap();
149 *stats = PipelineStats::default();
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::{PipelineStats, StatsManager};
156
157 #[test]
158 fn success_rate_handles_zero_processed() {
159 let stats = PipelineStats::default();
160 assert_eq!(stats.success_rate(), 0.0);
161 }
162
163 #[test]
164 fn success_rate_computes_percentage() {
165 let stats = PipelineStats {
166 total_processed: 10,
167 successful_predictions: 7,
168 failed_predictions: 3,
169 average_inference_time_ms: 50.0,
170 };
171 assert_eq!(stats.success_rate(), 70.0);
172 }
173
174 #[test]
175 fn failure_rate_handles_zero_processed() {
176 let stats = PipelineStats::default();
177 assert_eq!(stats.failure_rate(), 0.0);
178 }
179
180 #[test]
181 fn failure_rate_computes_percentage() {
182 let stats = PipelineStats {
183 total_processed: 8,
184 successful_predictions: 6,
185 failed_predictions: 2,
186 average_inference_time_ms: 75.0,
187 };
188 assert_eq!(stats.failure_rate(), 25.0);
189 }
190
191 #[test]
192 fn images_per_second_handles_zero_time() {
193 let stats = PipelineStats {
194 total_processed: 10,
195 successful_predictions: 10,
196 failed_predictions: 0,
197 average_inference_time_ms: 0.0,
198 };
199 assert_eq!(stats.images_per_second(), 0.0);
200 }
201
202 #[test]
203 fn images_per_second_computes_rate() {
204 let stats = PipelineStats {
205 total_processed: 10,
206 successful_predictions: 10,
207 failed_predictions: 0,
208 average_inference_time_ms: 100.0,
209 };
210 assert_eq!(stats.images_per_second(), 10.0);
211 }
212
213 #[test]
214 fn display_formats_metrics() {
215 let stats = PipelineStats {
216 total_processed: 10,
217 successful_predictions: 8,
218 failed_predictions: 2,
219 average_inference_time_ms: 125.0,
220 };
221
222 let display = stats.to_string();
223 assert!(display.contains("Pipeline Statistics:"));
224 assert!(display.contains("Total processed: 10"));
225 assert!(display.contains("Successful: 8 (80.0%)"));
226 assert!(display.contains("Failed: 2 (20.0%)"));
227 assert!(display.contains("Average inference time: 125.00 ms"));
228 assert!(display.contains("Processing speed: 8.00 images/sec"));
229 }
230
231 #[test]
232 fn stats_manager_updates_counters_and_average() {
233 let manager = StatsManager::new();
234
235 manager.update_stats(1, 1, 0, 100.0);
236 let stats = manager.get_stats();
237 assert_eq!(stats.total_processed, 1);
238 assert_eq!(stats.successful_predictions, 1);
239 assert_eq!(stats.failed_predictions, 0);
240 assert_eq!(stats.average_inference_time_ms, 100.0);
241
242 manager.update_stats(1, 0, 1, 200.0);
243 let stats = manager.get_stats();
244 assert_eq!(stats.total_processed, 2);
245 assert_eq!(stats.successful_predictions, 1);
246 assert_eq!(stats.failed_predictions, 1);
247 assert!((stats.average_inference_time_ms - 150.0).abs() < f64::EPSILON);
248 }
249
250 #[test]
251 fn stats_manager_resets_metrics() {
252 let manager = StatsManager::new();
253 manager.update_stats(5, 4, 1, 500.0);
254 manager.reset_stats();
255
256 let stats = manager.get_stats();
257 assert_eq!(stats.total_processed, 0);
258 assert_eq!(stats.successful_predictions, 0);
259 assert_eq!(stats.failed_predictions, 0);
260 assert_eq!(stats.average_inference_time_ms, 0.0);
261 }
262}