copybook_codec/lib_api/
run_summary.rs1use copybook_error::Error;
5use std::fmt;
6
7pub const MAX_CAPTURED_FAILURES: usize = 10;
13
14#[derive(Debug, Clone, PartialEq)]
16pub struct RecordFailure {
17 pub record_index: u64,
19 pub error: Error,
21}
22
23impl fmt::Display for RecordFailure {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(f, "record {}: {}", self.record_index, self.error)
26 }
27}
28
29#[derive(Debug, Default, Clone, PartialEq)]
34pub struct RunSummary {
35 pub records_processed: u64,
37 pub records_with_errors: u64,
39 pub warnings: u64,
41 pub processing_time_ms: u64,
43 pub bytes_processed: u64,
48 pub schema_fingerprint: String,
50 pub throughput_mbps: f64,
52 pub peak_memory_bytes: Option<u64>,
54 pub threads_used: usize,
56 pub failures: Vec<RecordFailure>,
61}
62
63impl RunSummary {
64 #[must_use]
66 pub fn new() -> Self {
67 Self::default()
68 }
69
70 #[must_use]
72 pub fn with_threads(threads: usize) -> Self {
73 Self {
74 threads_used: threads,
75 ..Self::default()
76 }
77 }
78
79 #[allow(clippy::cast_precision_loss)]
81 pub fn calculate_throughput(&mut self) {
82 if self.processing_time_ms > 0 {
83 let seconds = self.processing_time_ms as f64 / 1000.0;
84 let megabytes = self.bytes_processed as f64 / (1024.0 * 1024.0);
85 self.throughput_mbps = megabytes / seconds;
86 }
87 }
88
89 #[must_use]
91 pub const fn has_errors(&self) -> bool {
92 self.records_with_errors > 0
93 }
94
95 pub fn note_failure(&mut self, record_index: u64, error: &Error) {
102 self.records_with_errors += 1;
103 if self.failures.len() < MAX_CAPTURED_FAILURES {
104 self.failures.push(RecordFailure {
105 record_index,
106 error: error.clone(),
107 });
108 }
109 }
110
111 #[must_use]
113 pub fn undisclosed_failure_count(&self) -> u64 {
114 self.records_with_errors
115 .saturating_sub(self.failures.len() as u64)
116 }
117
118 #[must_use]
120 pub const fn has_warnings(&self) -> bool {
121 self.warnings > 0
122 }
123
124 #[must_use]
126 pub const fn is_successful(&self) -> bool {
127 !self.has_errors()
128 }
129
130 #[must_use]
132 pub const fn total_records(&self) -> u64 {
133 self.records_processed + self.records_with_errors
134 }
135
136 #[must_use]
138 #[allow(clippy::cast_precision_loss)]
139 pub fn success_rate(&self) -> f64 {
140 let total = self.total_records();
141 if total == 0 {
142 100.0
143 } else {
144 (self.records_processed as f64 / total as f64) * 100.0
145 }
146 }
147
148 #[must_use]
150 pub fn error_rate(&self) -> f64 {
151 100.0 - self.success_rate()
152 }
153
154 #[must_use]
156 #[allow(clippy::cast_precision_loss)]
157 pub fn processing_time_seconds(&self) -> f64 {
158 self.processing_time_ms as f64 / 1000.0
159 }
160
161 #[must_use]
163 #[allow(clippy::cast_precision_loss)]
164 pub fn bytes_processed_mb(&self) -> f64 {
165 self.bytes_processed as f64 / (1024.0 * 1024.0)
166 }
167
168 pub fn set_schema_fingerprint(&mut self, fingerprint: String) {
170 self.schema_fingerprint = fingerprint;
171 }
172
173 pub fn set_peak_memory_bytes(&mut self, bytes: u64) {
175 self.peak_memory_bytes = Some(bytes);
176 }
177}
178
179impl fmt::Display for RunSummary {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 writeln!(f, "Processing Summary:")?;
182 writeln!(f, " Records processed: {}", self.records_processed)?;
183 writeln!(f, " Records with errors: {}", self.records_with_errors)?;
184 writeln!(f, " Warnings: {}", self.warnings)?;
185 writeln!(f, " Success rate: {:.1}%", self.success_rate())?;
186 writeln!(
187 f,
188 " Processing time: {:.2}s",
189 self.processing_time_seconds()
190 )?;
191 writeln!(f, " Bytes processed: {:.2} MB", self.bytes_processed_mb())?;
192 writeln!(f, " Throughput: {:.2} MB/s", self.throughput_mbps)?;
193 writeln!(f, " Threads used: {}", self.threads_used)?;
194 if let Some(peak_memory) = self.peak_memory_bytes {
195 #[allow(clippy::cast_precision_loss)]
196 let peak_mb = peak_memory as f64 / (1024.0 * 1024.0);
197 writeln!(f, " Peak memory: {peak_mb:.2} MB")?;
198 }
199 if !self.schema_fingerprint.is_empty() {
200 writeln!(f, " Schema fingerprint: {}", self.schema_fingerprint)?;
201 }
202 Ok(())
203 }
204}