tracing_indicatif/
pb_manager.rs1use std::collections::VecDeque;
2use std::sync::atomic::AtomicUsize;
3use std::sync::Arc;
4use std::time::Duration;
5
6use indicatif::style::ProgressStyle;
7use indicatif::MultiProgress;
8use indicatif::ProgressBar;
9use indicatif::ProgressDrawTarget;
10use indicatif::ProgressState;
11use tracing_core::span;
12use tracing_core::Subscriber;
13use tracing_subscriber::layer;
14use tracing_subscriber::registry::LookupSpan;
15
16use crate::IndicatifSpanContext;
17
18#[derive(Clone)]
19struct RequireDefault;
20
21#[derive(Clone)]
36pub struct TickSettings {
37 pub term_draw_hz: u8,
42 pub default_tick_interval: Option<Duration>,
51 pub footer_tick_interval: Option<Duration>,
61 #[doc(hidden)]
63 #[allow(private_interfaces)]
64 pub require_default: RequireDefault,
65}
66
67impl Default for TickSettings {
68 fn default() -> Self {
69 Self {
70 term_draw_hz: 20,
71 default_tick_interval: Some(Duration::from_millis(100)),
72 footer_tick_interval: None,
73 require_default: RequireDefault,
74 }
75 }
76}
77
78pub(crate) struct ProgressBarManager {
79 pub(crate) mp: MultiProgress,
80 active_progress_bars: u64,
81 max_progress_bars: u64,
82 pending_progress_bars: Arc<AtomicUsize>,
85 pending_spans: VecDeque<span::Id>,
90 footer_pb: Option<ProgressBar>,
92 tick_settings: TickSettings,
93}
94
95impl ProgressBarManager {
96 pub(crate) fn new(
97 max_progress_bars: u64,
98 footer_progress_style: Option<ProgressStyle>,
99 tick_settings: TickSettings,
100 ) -> Self {
101 let mut s = Self {
102 mp: {
103 let mp = MultiProgress::new();
104 mp.set_draw_target(ProgressDrawTarget::stderr_with_hz(
105 tick_settings.term_draw_hz,
106 ));
107
108 mp
109 },
110 active_progress_bars: 0,
111 max_progress_bars: 0,
112 pending_progress_bars: Arc::new(AtomicUsize::new(0)),
113 pending_spans: VecDeque::new(),
114 footer_pb: None,
115 tick_settings,
116 };
117
118 s.set_max_progress_bars(max_progress_bars, footer_progress_style);
119
120 s
121 }
122
123 pub(crate) fn set_max_progress_bars(
124 &mut self,
125 max_progress_bars: u64,
126 footer_style: Option<ProgressStyle>,
127 ) {
128 self.max_progress_bars = max_progress_bars;
129
130 let pending_progress_bars = self.pending_progress_bars.clone();
131 self.footer_pb = footer_style.map(move |style| {
132 ProgressBar::hidden().with_style(style.with_key(
133 "pending_progress_bars",
134 move |_: &ProgressState, writer: &mut dyn std::fmt::Write| {
135 let _ = write!(
136 writer,
137 "{}",
138 pending_progress_bars.load(std::sync::atomic::Ordering::Acquire)
139 );
140 },
141 ))
142 });
143 }
144
145 pub(crate) fn set_tick_settings(&mut self, tick_settings: TickSettings) {
146 self.mp.set_draw_target(ProgressDrawTarget::stderr_with_hz(
147 tick_settings.term_draw_hz,
148 ));
149 self.tick_settings = tick_settings;
150 }
151
152 fn decrement_pending_pb(&mut self) {
153 let prev_val = self
154 .pending_progress_bars
155 .fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
156
157 if let Some(footer_pb) = self.footer_pb.as_ref() {
158 if prev_val == 1 {
160 debug_assert!(
161 !footer_pb.is_hidden(),
162 "footer progress bar was hidden despite there being pending progress bars"
163 );
164
165 if self.tick_settings.footer_tick_interval.is_some() {
166 footer_pb.disable_steady_tick();
167 }
168
169 footer_pb.finish_and_clear();
173 self.mp.remove(footer_pb);
174 } else {
175 footer_pb.tick();
176 }
177 }
178 }
179
180 fn add_pending_pb(&mut self, span_id: &span::Id) {
181 let prev_val = self
182 .pending_progress_bars
183 .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
184 self.pending_spans.push_back(span_id.clone());
185
186 if let Some(footer_pb) = self.footer_pb.as_ref() {
188 if prev_val == 0 {
189 debug_assert!(
190 footer_pb.is_hidden(),
191 "footer progress bar was not hidden despite there being no pending progress bars"
192 );
193
194 footer_pb.reset();
195
196 if let Some(tick_interval) = self.tick_settings.footer_tick_interval {
197 footer_pb.enable_steady_tick(tick_interval);
198 }
199
200 self.mp.add(footer_pb.clone());
201 }
205
206 footer_pb.tick();
207 }
208 }
209
210 pub(crate) fn show_progress_bar(
211 &mut self,
212 pb_span_ctx: &mut IndicatifSpanContext,
213 span_id: &span::Id,
214 ) {
215 if self.active_progress_bars < self.max_progress_bars {
216 let pb = match pb_span_ctx.parent_progress_bar {
217 Some(ref parent_pb) => self
220 .mp
221 .insert_after(parent_pb, pb_span_ctx.progress_bar.take().unwrap()),
222 None => {
223 if self
224 .footer_pb
225 .as_ref()
226 .map(|footer_pb| !footer_pb.is_hidden())
227 .unwrap_or(false)
228 {
229 self.mp
230 .insert_from_back(1, pb_span_ctx.progress_bar.take().unwrap())
231 } else {
232 self.mp.add(pb_span_ctx.progress_bar.take().unwrap())
233 }
234 }
235 };
236
237 self.active_progress_bars += 1;
238
239 if let Some(tick_interval) = self.tick_settings.default_tick_interval {
240 pb.enable_steady_tick(tick_interval);
241 }
242
243 pb.tick();
244
245 pb_span_ctx.progress_bar = Some(pb);
246 } else {
247 self.add_pending_pb(span_id);
248 }
249 }
250
251 pub(crate) fn finish_progress_bar<S>(
252 &mut self,
253 pb_span_ctx: &mut IndicatifSpanContext,
254 ctx: &layer::Context<'_, S>,
255 ) where
256 S: Subscriber + for<'a> LookupSpan<'a>,
257 {
258 let Some(pb) = pb_span_ctx.progress_bar.take() else {
259 return;
261 };
262
263 if pb.is_hidden() {
265 self.decrement_pending_pb();
266 return;
267 }
268
269 pb.finish_and_clear();
271 self.mp.remove(&pb);
272 self.active_progress_bars -= 1;
273
274 loop {
275 let Some(span_id) = self.pending_spans.pop_front() else {
276 break;
277 };
278
279 match ctx.span(&span_id) {
280 Some(next_eligible_span) => {
281 let mut ext = next_eligible_span.extensions_mut();
282 let indicatif_span_ctx = ext
283 .get_mut::<IndicatifSpanContext>()
284 .expect("No IndicatifSpanContext found; this is a bug");
285
286 if indicatif_span_ctx.progress_bar.is_none() {
292 continue;
293 }
294
295 self.decrement_pending_pb();
296 self.show_progress_bar(indicatif_span_ctx, &span_id);
297 break;
298 }
299 None => {
300 continue;
302 }
303 }
304 }
305 }
306}