simple_git/
progress_tracing.rs1use 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 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}