Skip to content

Commit 9d660b9

Browse files
authored
fix: make the progress bar display more clearly (#1247)
1 parent 0296c97 commit 9d660b9

File tree

1 file changed

+87
-109
lines changed

1 file changed

+87
-109
lines changed

src/execution/stats.rs

Lines changed: 87 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,6 @@ pub struct UpdateStats {
107107
pub num_errors: Counter,
108108
/// Processing counters for tracking in-process rows.
109109
pub processing: ProcessingCounters,
110-
/// Cumulative total count of items (for display purposes)
111-
/// This represents the actual total after applying additions and deletions
112-
#[serde(skip)]
113-
pub cumulative_total: Counter,
114110
}
115111

116112
impl UpdateStats {
@@ -123,7 +119,6 @@ impl UpdateStats {
123119
num_reprocesses: self.num_reprocesses.delta(&base.num_reprocesses),
124120
num_errors: self.num_errors.delta(&base.num_errors),
125121
processing: self.processing.delta(&base.processing),
126-
cumulative_total: self.cumulative_total.clone(),
127122
}
128123
}
129124

@@ -135,9 +130,6 @@ impl UpdateStats {
135130
self.num_reprocesses.merge(&delta.num_reprocesses);
136131
self.num_errors.merge(&delta.num_errors);
137132
self.processing.merge(&delta.processing);
138-
// Update cumulative total: add insertions, subtract deletions
139-
let net_change = delta.num_insertions.get() - delta.num_deletions.get();
140-
self.cumulative_total.inc(net_change);
141133
}
142134

143135
pub fn has_any_change(&self) -> bool {
@@ -199,113 +191,96 @@ impl OperationInProcessStats {
199191
}
200192
}
201193

194+
struct UpdateStatsSegment {
195+
count: i64,
196+
symbol: char,
197+
label: &'static str,
198+
bar_width: usize,
199+
}
200+
201+
impl UpdateStatsSegment {
202+
pub fn new(count: i64, symbol: char, label: &'static str) -> Self {
203+
Self {
204+
count,
205+
symbol,
206+
label,
207+
bar_width: 0,
208+
}
209+
}
210+
}
211+
212+
const BAR_WIDTH: usize = 40;
213+
214+
fn indices_of<T, const N: usize>(_: &[T; N]) -> [usize; N] {
215+
std::array::from_fn(|i| i)
216+
}
202217
impl std::fmt::Display for UpdateStats {
203218
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204-
let mut segments = Vec::new();
205-
let num_insertions = self.num_insertions.get();
206-
let num_deletions = self.num_deletions.get();
207-
let num_updates = self.num_updates.get();
208-
let num_no_change = self.num_no_change.get();
209-
let num_errors = self.num_errors.get();
219+
let mut segments: [UpdateStatsSegment; _] = [
220+
UpdateStatsSegment::new(self.num_insertions.get(), '+', "added"),
221+
UpdateStatsSegment::new(self.num_updates.get(), '~', "updated"),
222+
UpdateStatsSegment::new(self.num_reprocesses.get(), '*', "reprocessed"),
223+
UpdateStatsSegment::new(self.num_deletions.get(), '-', "deleted"),
224+
UpdateStatsSegment::new(self.num_no_change.get(), '.', "no change"),
225+
UpdateStatsSegment::new(self.num_errors.get(), '!', "errors"),
226+
];
210227
let num_in_process = self.processing.get_in_process();
211-
let num_reprocesses = self.num_reprocesses.get();
212-
let total = num_insertions + num_deletions + num_updates + num_no_change + num_reprocesses;
228+
let processed_count = segments.iter().map(|seg| seg.count).sum::<i64>();
229+
let total = num_in_process + processed_count;
213230

214-
// Progress bar segments
215-
if total > 0 {
216-
if num_insertions > 0 {
217-
segments.push((num_insertions, "+", format!("(+{} added)", num_insertions)));
218-
}
219-
if num_deletions > 0 {
220-
segments.push((num_deletions, "-", format!("(-{} removed)", num_deletions)));
221-
}
222-
if num_updates > 0 {
223-
segments.push((num_updates, "~", format!("(~{} updated)", num_updates)));
224-
}
225-
if num_no_change > 0 {
226-
segments.push((num_no_change, " ", "".to_string()));
227-
}
231+
if total <= 0 {
232+
write!(f, "No input data")?;
233+
return Ok(());
228234
}
229235

230-
// Error handling
231-
if num_errors > 0 {
232-
write!(f, "{} rows failed", num_errors)?;
233-
if !segments.is_empty() {
234-
write!(f, "; ")?;
236+
let mut segments_indices = indices_of(&segments);
237+
segments_indices.sort_by_key(|&i| segments[i].count);
238+
239+
let mut remaining_width = BAR_WIDTH as u64;
240+
let mut remaining_count = total as u64;
241+
for idx in segments_indices.iter() {
242+
let seg = &mut segments[*idx];
243+
if seg.count > 0 {
244+
if remaining_count == 0 {
245+
error!("remaining_count is 0, but still have segments to process");
246+
break;
247+
}
248+
let width = std::cmp::max(
249+
// rounded division of remaining_width * seg.count / remaining_count
250+
(remaining_width * (seg.count as u64) + remaining_count / 2) / remaining_count,
251+
1,
252+
);
253+
seg.bar_width = width as usize;
254+
remaining_width -= width;
255+
remaining_count -= seg.count as u64;
235256
}
236257
}
237258

238-
// Progress bar
239-
if !segments.is_empty() {
240-
let mut sorted_segments = segments.clone();
241-
sorted_segments.sort_by_key(|s| s.0);
242-
sorted_segments.reverse();
243-
244-
let bar_width = 40;
245-
let mut bar = String::new();
246-
247-
let mut remaining_width = bar_width;
248-
249-
for (count, segment_type, _) in sorted_segments.iter() {
250-
let segment_width = (*count * bar_width as i64 / total as i64) as usize;
251-
let width = std::cmp::min(segment_width, remaining_width);
252-
if width > 0 {
253-
// Calculate completed and remaining portions
254-
let completed_portion =
255-
(width as f64 * (total - num_in_process) as f64 / total as f64) as usize;
256-
let remaining_portion = width - completed_portion;
257-
258-
// Add segment with appropriate characters based on type
259-
if completed_portion > 0 {
260-
let completed_char = match *segment_type {
261-
"+" => "█",
262-
"-" => "▓",
263-
"~" => "▒",
264-
_ => "░",
265-
};
266-
bar.push_str(&completed_char.repeat(completed_portion));
267-
}
268-
269-
if remaining_portion > 0 {
270-
let remaining_char = match *segment_type {
271-
"+" => "▒",
272-
"-" => "░",
273-
"~" => "░",
274-
_ => " ",
275-
};
276-
bar.push_str(&remaining_char.repeat(remaining_portion));
277-
}
278-
279-
remaining_width = remaining_width.saturating_sub(width);
280-
}
281-
}
282-
if remaining_width > 0 {
283-
bar.push_str(&" ".repeat(remaining_width));
259+
write!(f, "[")?;
260+
for segment in segments.iter() {
261+
for _ in 0..segment.bar_width {
262+
write!(f, "{}", segment.symbol)?;
284263
}
285-
// Use total from current operations - this represents the actual record count
286-
write!(f, "[{}] {}/{} records ", bar, total - num_in_process, total)?;
287-
288-
// Add segment labels with different grey shades for each segment type
289-
let mut first = true;
290-
for (_, segment_type, label) in segments.iter() {
291-
if !label.is_empty() {
292-
if !first {
293-
write!(f, " ")?;
294-
}
295-
write!(f, "{}", label)?;
296-
first = false;
297-
}
298-
}
299-
} else {
300-
write!(f, "No changes")?;
301264
}
302-
303-
// In-process info with grey coloring
304-
if num_in_process > 0 {
305-
if !segments.is_empty() {
306-
write!(f, " ")?;
265+
for _ in 0..remaining_width {
266+
write!(f, " ")?;
267+
}
268+
write!(f, "] {processed_count}/{total} source rows")?;
269+
270+
if processed_count > 0 {
271+
let mut delimiter = ':';
272+
for seg in segments.iter() {
273+
if seg.count > 0 {
274+
write!(
275+
f,
276+
"{delimiter} ({symbol}) {count} {label}",
277+
count = seg.count,
278+
symbol = seg.symbol,
279+
label = seg.label,
280+
)?;
281+
delimiter = ',';
282+
}
307283
}
308-
write!(f, "({} in process)", num_in_process)?;
309284
}
310285

311286
Ok(())
@@ -575,21 +550,24 @@ mod tests {
575550
let stats = UpdateStats::default();
576551

577552
// Test with no activity
578-
assert_eq!(format!("{}", stats), "No changes");
553+
assert_eq!(format!("{}", stats), "No input data");
579554

580555
// Test with in-process rows (no segments yet, so just shows in-process)
581556
stats.processing.start(5);
582557
let display = format!("{}", stats);
583-
assert!(display.contains("5 in process"));
558+
assert_eq!(
559+
display,
560+
"[ ] 0/5 source rows"
561+
);
584562

585563
// Test with mixed activity
586564
stats.num_insertions.inc(3);
587565
stats.num_errors.inc(1);
588-
stats.cumulative_total.inc(3);
589566
let display = format!("{}", stats);
590-
assert!(display.contains("1 rows failed"));
591-
assert!(display.contains("(+3 added)"));
592-
assert!(display.contains("5 in process"));
567+
assert_eq!(
568+
display,
569+
"[++++++++++++++!!!! ] 4/9 source rows: (+) 3 added, (!) 1 errors"
570+
);
593571
}
594572

595573
#[test]

0 commit comments

Comments
 (0)