Skip to main content

qubit_progress/model/
progress_metric_snapshot.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use std::time::Duration;
11
12use serde::{
13    Deserialize,
14    Serialize,
15};
16
17use super::{
18    ProgressCounter,
19    ProgressMetric,
20    ProgressPhase,
21    ProgressStage,
22};
23
24/// Snapshot of one metric counter within a progress event.
25///
26/// A progress event may carry multiple counters using different metric units,
27/// such as entries and bytes. `ProgressMetricSnapshot` flattens one counter
28/// together with the event-level phase, stage, and elapsed time so formatters
29/// and consumers can handle one metric record at a time.
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct ProgressMetricSnapshot {
32    /// Complete metric metadata for this snapshot.
33    metric: ProgressMetric,
34    /// Lifecycle phase inherited from the source progress event.
35    phase: ProgressPhase,
36    /// Optional stage inherited from the source progress event.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    stage: Option<ProgressStage>,
39    /// Total work-unit count when known.
40    total_count: Option<u64>,
41    /// Completed work-unit count.
42    completed_count: u64,
43    /// Active work-unit count.
44    active_count: u64,
45    /// Successful work-unit count.
46    succeeded_count: u64,
47    /// Failed work-unit count.
48    failed_count: u64,
49    /// Monotonic elapsed duration inherited from the source progress event.
50    #[serde(with = "qubit_serde::serde::duration_with_unit")]
51    elapsed: Duration,
52}
53
54impl ProgressMetricSnapshot {
55    /// Creates a metric snapshot from explicit values.
56    ///
57    /// # Parameters
58    ///
59    /// * `metric` - Complete metric metadata.
60    /// * `phase` - Lifecycle phase inherited from the source event.
61    /// * `stage` - Optional stage inherited from the source event.
62    /// * `counter` - Counter values copied into the snapshot.
63    /// * `elapsed` - Elapsed duration inherited from the source event.
64    ///
65    /// # Returns
66    ///
67    /// A flattened metric snapshot.
68    #[inline]
69    pub fn new(
70        metric: ProgressMetric,
71        phase: ProgressPhase,
72        stage: Option<ProgressStage>,
73        counter: &ProgressCounter,
74        elapsed: Duration,
75    ) -> Self {
76        Self {
77            metric,
78            phase,
79            stage,
80            total_count: counter.total_count(),
81            completed_count: counter.completed_count(),
82            active_count: counter.active_count(),
83            succeeded_count: counter.succeeded_count(),
84            failed_count: counter.failed_count(),
85            elapsed,
86        }
87    }
88
89    /// Returns the complete metric metadata.
90    ///
91    /// # Returns
92    ///
93    /// The metric metadata associated with this snapshot.
94    #[inline]
95    pub const fn metric(&self) -> &ProgressMetric {
96        &self.metric
97    }
98
99    /// Returns the stable metric id.
100    ///
101    /// # Returns
102    ///
103    /// The metric id associated with this snapshot.
104    #[inline]
105    pub fn metric_id(&self) -> &str {
106        self.metric.id()
107    }
108
109    /// Returns the human-readable metric name.
110    ///
111    /// # Returns
112    ///
113    /// The metric display name associated with this snapshot.
114    #[inline]
115    pub fn metric_name(&self) -> &str {
116        self.metric.name()
117    }
118
119    /// Returns the lifecycle phase.
120    ///
121    /// # Returns
122    ///
123    /// The phase inherited from the source event.
124    #[inline]
125    pub const fn phase(&self) -> ProgressPhase {
126        self.phase
127    }
128
129    /// Returns the optional stage.
130    ///
131    /// # Returns
132    ///
133    /// `Some(stage)` when the source event carried stage metadata, otherwise
134    /// `None`.
135    #[inline]
136    pub const fn stage(&self) -> Option<&ProgressStage> {
137        self.stage.as_ref()
138    }
139
140    /// Returns the total work-unit count when known.
141    ///
142    /// # Returns
143    ///
144    /// `Some(total)` for known-total progress, or `None` for open-ended progress.
145    #[inline]
146    pub const fn total_count(&self) -> Option<u64> {
147        self.total_count
148    }
149
150    /// Returns the completed work-unit count.
151    ///
152    /// # Returns
153    ///
154    /// The number of completed work units.
155    #[inline]
156    pub const fn completed_count(&self) -> u64 {
157        self.completed_count
158    }
159
160    /// Returns the active work-unit count.
161    ///
162    /// # Returns
163    ///
164    /// The number of currently active work units.
165    #[inline]
166    pub const fn active_count(&self) -> u64 {
167        self.active_count
168    }
169
170    /// Returns the successful work-unit count.
171    ///
172    /// # Returns
173    ///
174    /// The number of successful work units.
175    #[inline]
176    pub const fn succeeded_count(&self) -> u64 {
177        self.succeeded_count
178    }
179
180    /// Returns the failed work-unit count.
181    ///
182    /// # Returns
183    ///
184    /// The number of failed work units.
185    #[inline]
186    pub const fn failed_count(&self) -> u64 {
187        self.failed_count
188    }
189
190    /// Returns the remaining work-unit count when the total is known.
191    ///
192    /// # Returns
193    ///
194    /// `Some(total - completed - active)` using saturating arithmetic for
195    /// known-total progress, or `None` when the total is unknown.
196    #[inline]
197    pub const fn remaining_count(&self) -> Option<u64> {
198        match self.total_count {
199            Some(total_count) => Some(
200                total_count
201                    .saturating_sub(self.completed_count)
202                    .saturating_sub(self.active_count),
203            ),
204            None => None,
205        }
206    }
207
208    /// Returns completed progress as a fraction in `0.0..=1.0`.
209    ///
210    /// # Returns
211    ///
212    /// `Some(fraction)` for known-total progress, `Some(1.0)` when the known
213    /// total is zero, or `None` when the total is unknown.
214    #[inline]
215    pub fn progress_fraction(&self) -> Option<f64> {
216        self.total_count.map(|total_count| {
217            if total_count == 0 {
218                1.0
219            } else {
220                (self.completed_count as f64 / total_count as f64).clamp(0.0, 1.0)
221            }
222        })
223    }
224
225    /// Returns completed progress as a percentage in `0.0..=100.0`.
226    ///
227    /// # Returns
228    ///
229    /// `Some(percent)` for known-total progress, or `None` when the total is
230    /// unknown.
231    #[inline]
232    pub fn progress_percent(&self) -> Option<f64> {
233        self.progress_fraction().map(|fraction| fraction * 100.0)
234    }
235
236    /// Returns the elapsed duration.
237    ///
238    /// # Returns
239    ///
240    /// The elapsed duration inherited from the source event.
241    #[inline]
242    pub const fn elapsed(&self) -> Duration {
243        self.elapsed
244    }
245}