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}