simple_git/
progress_tracing.rs

1use std::{
2    sync::{
3        atomic::{AtomicBool, Ordering},
4        Arc,
5    },
6    time::Duration,
7};
8
9use compact_str::{format_compact, CompactString};
10use gix::progress::{
11    prodash::messages::MessageLevel, Count, Id, NestedProgress, Progress, Step, StepShared, Unit,
12    UNKNOWN,
13};
14use tokio::time;
15use tracing::{error, info};
16
17pub(super) struct TracingProgress {
18    name: CompactString,
19    id: Id,
20    max: Option<usize>,
21    unit: Option<Unit>,
22    step: StepShared,
23    trigger: Arc<AtomicBool>,
24}
25
26const EMIT_LOG_EVERY_S: f32 = 0.5;
27const SEP: &str = "::";
28
29impl TracingProgress {
30    /// Create a new instanCompactce from `name`.
31    pub fn new(name: &str) -> Self {
32        let trigger = Arc::new(AtomicBool::new(true));
33        tokio::spawn({
34            let mut interval = time::interval(Duration::from_secs_f32(EMIT_LOG_EVERY_S));
35            interval.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
36
37            let trigger = Arc::clone(&trigger);
38            async move {
39                while Arc::strong_count(&trigger) > 1 {
40                    trigger.store(true, Ordering::Relaxed);
41
42                    interval.tick().await;
43                }
44            }
45        });
46        Self {
47            name: CompactString::new(name),
48            id: UNKNOWN,
49            max: None,
50            step: Default::default(),
51            unit: None,
52            trigger,
53        }
54    }
55
56    fn log_progress(&self, step: Step) {
57        if self.trigger.swap(false, Ordering::Relaxed) {
58            match (self.max, &self.unit) {
59                (max, Some(unit)) => {
60                    info!("{} → {}", self.name, unit.display(step, max, None))
61                }
62                (Some(max), None) => info!("{} → {} / {}", self.name, step, max),
63                (None, None) => info!("{} → {}", self.name, step),
64            }
65        }
66    }
67}
68
69impl Count for TracingProgress {
70    fn set(&self, step: Step) {
71        self.step.store(step, Ordering::Relaxed);
72        self.log_progress(step);
73    }
74
75    fn step(&self) -> Step {
76        self.step.load(Ordering::Relaxed)
77    }
78
79    fn inc_by(&self, step_to_inc: Step) {
80        self.log_progress(
81            self.step
82                .fetch_add(step_to_inc, Ordering::Relaxed)
83                .wrapping_add(step_to_inc),
84        );
85    }
86
87    fn counter(&self) -> StepShared {
88        Arc::clone(&self.step)
89    }
90}
91
92impl Progress for TracingProgress {
93    fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {
94        self.max = max;
95        self.unit = unit;
96    }
97
98    fn unit(&self) -> Option<Unit> {
99        self.unit.clone()
100    }
101
102    fn max(&self) -> Option<usize> {
103        self.max
104    }
105
106    fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
107        let prev = self.max;
108        self.max = max;
109        prev
110    }
111
112    fn set_name(&mut self, name: String) {
113        self.name = self
114            .name
115            .split("::")
116            .next()
117            .map(|parent| format_compact!("{parent}{SEP}{name}"))
118            .unwrap_or_else(|| name.into());
119    }
120
121    fn name(&self) -> Option<String> {
122        self.name.split(SEP).nth(1).map(ToOwned::to_owned)
123    }
124
125    fn id(&self) -> Id {
126        self.id
127    }
128
129    fn message(&self, level: MessageLevel, message: String) {
130        let name = &self.name;
131        match level {
132            MessageLevel::Info => info!("ℹ{name} → {message}"),
133            MessageLevel::Failure => error!("𐄂{name} → {message}"),
134            MessageLevel::Success => info!("✓{name} → {message}"),
135        }
136    }
137}
138
139impl NestedProgress for TracingProgress {
140    type SubProgress = TracingProgress;
141
142    fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {
143        self.add_child_with_id(name, UNKNOWN)
144    }
145
146    fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {
147        Self {
148            name: format_compact!("{}{}{}", self.name, SEP, Into::<String>::into(name)),
149            id,
150            step: Arc::default(),
151            max: None,
152            unit: None,
153            trigger: Arc::clone(&self.trigger),
154        }
155    }
156}