Skip to main content

copybook_codec/lib_api/
run_summary.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2//! Processing run statistics and display formatting.
3
4use std::fmt;
5
6/// Summary of a processing run with comprehensive statistics.
7///
8/// Captures record counts, error rates, throughput, and resource usage
9/// for a complete decode or encode operation.
10#[derive(Debug, Default, Clone, PartialEq)]
11pub struct RunSummary {
12    /// Total number of records decoded or encoded successfully.
13    pub records_processed: u64,
14    /// Number of records that encountered errors during processing.
15    pub records_with_errors: u64,
16    /// Number of non-fatal warnings generated during processing.
17    pub warnings: u64,
18    /// Wall-clock processing time in milliseconds.
19    pub processing_time_ms: u64,
20    /// Total bytes read from input.
21    ///
22    /// Fixed records count payload bytes; RDW records count header-plus-payload
23    /// physical bytes.
24    pub bytes_processed: u64,
25    /// SHA-256 fingerprint of the schema used for processing.
26    pub schema_fingerprint: String,
27    /// Processing throughput in MiB/s.
28    pub throughput_mbps: f64,
29    /// Peak memory usage in bytes, if available from the runtime.
30    pub peak_memory_bytes: Option<u64>,
31    /// Number of worker threads used for parallel processing.
32    pub threads_used: usize,
33}
34
35impl RunSummary {
36    /// Create a new run summary with default values
37    #[must_use]
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Create a new run summary with specified thread count
43    #[must_use]
44    pub fn with_threads(threads: usize) -> Self {
45        Self {
46            threads_used: threads,
47            ..Self::default()
48        }
49    }
50
51    /// Calculate throughput based on bytes and time
52    #[allow(clippy::cast_precision_loss)]
53    pub fn calculate_throughput(&mut self) {
54        if self.processing_time_ms > 0 {
55            let seconds = self.processing_time_ms as f64 / 1000.0;
56            let megabytes = self.bytes_processed as f64 / (1024.0 * 1024.0);
57            self.throughput_mbps = megabytes / seconds;
58        }
59    }
60
61    /// Check if processing had any errors
62    #[must_use]
63    pub const fn has_errors(&self) -> bool {
64        self.records_with_errors > 0
65    }
66
67    /// Check if processing had any warnings
68    #[must_use]
69    pub const fn has_warnings(&self) -> bool {
70        self.warnings > 0
71    }
72
73    /// Check if processing was successful (no errors)
74    #[must_use]
75    pub const fn is_successful(&self) -> bool {
76        !self.has_errors()
77    }
78
79    /// Get the total number of records attempted (processed + errors)
80    #[must_use]
81    pub const fn total_records(&self) -> u64 {
82        self.records_processed + self.records_with_errors
83    }
84
85    /// Get the success rate as a percentage (0.0 to 100.0)
86    #[must_use]
87    #[allow(clippy::cast_precision_loss)]
88    pub fn success_rate(&self) -> f64 {
89        let total = self.total_records();
90        if total == 0 {
91            100.0
92        } else {
93            (self.records_processed as f64 / total as f64) * 100.0
94        }
95    }
96
97    /// Get the error rate as a percentage (0.0 to 100.0)
98    #[must_use]
99    pub fn error_rate(&self) -> f64 {
100        100.0 - self.success_rate()
101    }
102
103    /// Get processing time in seconds
104    #[must_use]
105    #[allow(clippy::cast_precision_loss)]
106    pub fn processing_time_seconds(&self) -> f64 {
107        self.processing_time_ms as f64 / 1000.0
108    }
109
110    /// Get bytes processed in megabytes
111    #[must_use]
112    #[allow(clippy::cast_precision_loss)]
113    pub fn bytes_processed_mb(&self) -> f64 {
114        self.bytes_processed as f64 / (1024.0 * 1024.0)
115    }
116
117    /// Set the schema fingerprint
118    pub fn set_schema_fingerprint(&mut self, fingerprint: String) {
119        self.schema_fingerprint = fingerprint;
120    }
121
122    /// Set the peak memory usage
123    pub fn set_peak_memory_bytes(&mut self, bytes: u64) {
124        self.peak_memory_bytes = Some(bytes);
125    }
126}
127
128impl fmt::Display for RunSummary {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        writeln!(f, "Processing Summary:")?;
131        writeln!(f, "  Records processed: {}", self.records_processed)?;
132        writeln!(f, "  Records with errors: {}", self.records_with_errors)?;
133        writeln!(f, "  Warnings: {}", self.warnings)?;
134        writeln!(f, "  Success rate: {:.1}%", self.success_rate())?;
135        writeln!(
136            f,
137            "  Processing time: {:.2}s",
138            self.processing_time_seconds()
139        )?;
140        writeln!(f, "  Bytes processed: {:.2} MB", self.bytes_processed_mb())?;
141        writeln!(f, "  Throughput: {:.2} MB/s", self.throughput_mbps)?;
142        writeln!(f, "  Threads used: {}", self.threads_used)?;
143        if let Some(peak_memory) = self.peak_memory_bytes {
144            #[allow(clippy::cast_precision_loss)]
145            let peak_mb = peak_memory as f64 / (1024.0 * 1024.0);
146            writeln!(f, "  Peak memory: {peak_mb:.2} MB")?;
147        }
148        if !self.schema_fingerprint.is_empty() {
149            writeln!(f, "  Schema fingerprint: {}", self.schema_fingerprint)?;
150        }
151        Ok(())
152    }
153}