mpb (Multi Progress Bar)
repository·master·Indexed 25 days ago
https://github.com/vbauerster/mpbA Go library for rendering multiple, dynamic progress bars in terminal applications. It supports concurrent bars, dynamic total updates, and built-in decorators for metrics such as percentage, byte counters, and EWMA-based ETA. The library provides flexible customization through BarStyleComposer for visual styles, SpinnerStyleComposer for indeterminate progress, and support for synchronized decorator widths across multiple bars.
What's inside mpb
- mpb is a Go library designed for rendering progress bars in terminal applications. It supports multiple concurrent bars, dynamic total updates, and the ability to add or remove bars while the rendering process is active. It also features built-in decorators for common metrics like elapsed time, EWMA-based ETA, percentage, and byte counters, with support for synchronized decorator widths across multiple bars.
Wrap an io.Writer to update progress with a Bar
masterTo automatically update a progress bar as data is written to anio.Writer, you can use a proxy writer. This wrapper interceptsWritecalls and callsbar.IncrBy(n)wherenis the number of bytes written. If the underlying writer implementsio.WriteCloser, the proxy'sClose()method will also close the underlying writer. If the underlying writer implementsio.ReaderFrom, the proxy will use a specializedReadFromimplementation to ensure progress is correctly tracked during large transfers.Wrap an io.Reader to update progress automatically
masterThe
mpbpackage provides internal mechanisms (vianewProxyReader) to wrap anio.Readerso that progress bar updates are triggered automatically as data is read.When a reader is wrapped:
- Every call to
Read(p []byte)increments the progress bar by the number of bytes read (n). - If the reader implements
io.WriterTo, the wrapper also implementsio.WriterToto ensure efficient data transfer while maintaining progress updates. - If the underlying reader is an
io.ReadCloser, the wrapper'sClose()method will correctly close the underlying resource.
- Every call to
Update progress using EWMA for smoother rate estimation
masterWhen you need more accurate rate estimation (e.g., for displaying speed in MB/s), use the EWMA (Exponentially Weighted Moving Average) proxy writer. Instead of a simple increment, it usesbar.EwmaIncrBy(n, duration)to track the time taken for each write operation. This allows the progress bar to calculate a smoothed moving average of the transfer speed. Like the standard proxy, it also supportsio.WriteCloserandio.ReaderFrominterfaces.Use EWMA for progress bar speed estimation
masterWhen using the EWMA (Exponentially Weighted Moving Average) variant of the progress reader, the progress bar doesn't just track bytes read, but also tracks the time taken for each read operation. This allows the progress bar to provide more accurate speed/rate estimations.
EwmaIncrBy(n, duration)is used internally to update the bar with both the byte count and the elapsed time since the start of the read operation.
Create custom spinner-style progress bars with SpinnerStyleComposer
masterUse
SpinnerStyleComposerto build aBarFillerthat displays an animated spinner instead of a traditional progress bar. This is useful for tasks where the exact progress percentage is unknown or not applicable.You can customize the animation frames, the position of the spinner (left, right, or centered), and apply a metadata transformation function.
To use it, call
SpinnerStyle(...)to initialize the composer, chain configuration methods, and then call.Build()to get theBarFiller.Initialize a Progress container
masterUse
NeworNewWithContextto create aProgressinstance. TheProgressinstance acts as a container that manages the rendering and lifecycle of multiple progress bars or spinners.Important: A
Progressinstance cannot be reused after theWait()method has been called. If you attempt to add bars or write to it afterWait(), you will receiveErrDone.Render a single progress bar
masterTo render a single progress bar, initialize a progress container using
mpb.New(). You can customize the container width usingmpb.WithWidth(). Create the bar usingp.New(total, ...)and configure its appearance usingmpb.BarStyle()and decorators viampb.PrependDecorators()andmpb.AppendDecorators(). Finally, callp.Wait()to ensure the bar is flushed and the rendering process completes.package main import ( "math/rand" "time" "github.com/vbauerster/mpb/v8" "github.com/vbauerster/mpb/v8/decor" ) func main() { // initialize progress container, with custom width p := mpb.New(mpb.WithWidth(64)) total := 100 name := "Single Bar:" // create a single bar, which will inherit container's width bar := p.New(int64(total), // BarFillerBuilder with custom style mpb.BarStyle().Lbound("╢").Filler("▌").Tip("▌").Padding("░").Rbound("╟"), mpb.PrependDecorators( // display our name with one space on the right decor.Name(name, decor.WC{C: decor.DindentRight | decor.DextraSpace}), // replace ETA decorator with "done" message, OnComplete event decor.OnComplete(decor.AverageETA(decor.ET_STYLE_GO), "done"), ), mpb.AppendDecorators(decor.Percentage()), ) // simulating some work max := 100 * time.Millisecond for range total { time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10) bar.Increment() } // wait for our bar to complete and flush p.Wait() }Render multiple progress bars concurrently
masterTo manage multiple bars, you can pass a
sync.WaitGrouptompb.New(mpb.WithWaitGroup(&wg)). This allowsp.Wait()to block until both the progress bars are finished and the WaitGroup is done. Usep.AddBar(total, ...)to add new bars to the container. For accurate EWMA (Exponentially Weighted Moving Average) ETA calculations, usebar.EwmaIncrement(duration)instead of the standardIncrement()to provide the time elapsed since the last iteration.var wg sync.WaitGroup // passed wg will be accounted at p.Wait() call p := mpb.New(mpb.WithWaitGroup(&wg)) total, numBars := 100, 3 wg.Add(numBars) for i := range numBars { name := fmt.Sprintf("Bar#%d:", i) bar := p.AddBar(int64(total), mpb.PrependDecorators( // simple name decorator decor.Name(name), // decor.DSyncWidth bit enables column width synchronization decor.Percentage(decor.WCSyncSpace), ), mpb.AppendDecorators( // replace ETA decorator with "done" message, OnComplete event decor.OnComplete( // ETA decorator with ewma age of 30 decor.EwmaETA(decor.ET_STYLE_GO, 30, decor.WCSyncWidth), "done", ), ), ) // simulating some work go func() { defer wg.Done() rng := rand.New(rand.NewSource(time.Now().UnixNano())) max := 100 * time.Millisecond for range total { // start variable is solely for EWMA calculation // EWMA's unit of measure is an iteration's duration start := time.Now() time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10) // we need to call EwmaIncrement to fulfill ewma decorator's contract bar.EwmaIncrement(time.Since(start)) } }() } // wait for passed wg and for all bars to complete and flush p.Wait()Manage bar lifecycle and queuing
masterControl how bars behave when they complete, abort, or are queued:
BarQueueAfter(bar *Bar): Places the current bar in a queue to start after the specifiedbarcompletes or aborts. The current bar inherits the priority of the argument bar.BarRemoveOnComplete(): Removes both the bar's filler and its decorators once the bar completes. This is ineffective if the container'sPopCompletedModeis enabled.BarNoPop(): Disables the bar from
Extend bars with arbitrary lines using BarExtender
masterThe
BarExtenderoption allows you to extend a bar with arbitrary lines. The providedBarFillerwill be called during every render/flush cycle. Any lines written to the underlyingio.Writerwill extend the bar either above or below the bar itself.BarExtender(filler BarFiller, rev bool):filler: TheBarFillerused to generate the lines.rev: Iftrue, lines are added in the
Add progress bars and spinners
masterYou can add bars to a
Progresscontainer using several methods depending on the desired visual style:AddBar(total int64, options ...BarOption): Creates a bar with the default bar filler.AddSpinner(total int64, options ...BarOption): Creates a bar with the default spinner filler.New(total int64, builder BarFillerBuilder, options ...BarOption): Creates a bar using a customBarFillerBuilder.MustAdd(total int64, filler BarFiller, options ...BarOption): Same asNew, but panics if the container is already closed (afterWait()).Add(total int64, filler BarFiller, options ...BarOption): The base method for adding a bar with a specificBarFiller. Returns(nil, ErrDone)if called afterWait().