mpb (Multi Progress Bar)

repository·master·Indexed 25 days ago

https://github.com/vbauerster/mpb

A 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.

Tokens
6.6K
Snippets
3
Records
46
Agent score
82%

What's inside mpb

  1. Overview of Multi Progress Bar (mpb)

    master
    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.
  2. Wrap an io.Writer to update progress with a Bar

    master
    To automatically update a progress bar as data is written to an io.Writer, you can use a proxy writer. This wrapper intercepts Write calls and calls bar.IncrBy(n) where n is the number of bytes written. If the underlying writer implements io.WriteCloser, the proxy's Close() method will also close the underlying writer. If the underlying writer implements io.ReaderFrom, the proxy will use a specialized ReadFrom implementation to ensure progress is correctly tracked during large transfers.
  3. Wrap an io.Reader to update progress automatically

    master

    The mpb package provides internal mechanisms (via newProxyReader) to wrap an io.Reader so that progress bar updates are triggered automatically as data is read.

    When a reader is wrapped:

    1. Every call to Read(p []byte) increments the progress bar by the number of bytes read (n).
    2. If the reader implements io.WriterTo, the wrapper also implements io.WriterTo to ensure efficient data transfer while maintaining progress updates.
    3. If the underlying reader is an io.ReadCloser, the wrapper's Close() method will correctly close the underlying resource.
  4. Update progress using EWMA for smoother rate estimation

    master
    When 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 uses bar.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 supports io.WriteCloser and io.ReaderFrom interfaces.
  5. Use EWMA for progress bar speed estimation

    master

    When 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.
  6. Create custom spinner-style progress bars with SpinnerStyleComposer

    master

    Use SpinnerStyleComposer to build a BarFiller that 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 the BarFiller.

  7. Initialize a Progress container

    master

    Use New or NewWithContext to create a Progress instance. The Progress instance acts as a container that manages the rendering and lifecycle of multiple progress bars or spinners.

    Important: A Progress instance cannot be reused after the Wait() method has been called. If you attempt to add bars or write to it after Wait(), you will receive ErrDone.

  8. Render a single progress bar

    master

    To render a single progress bar, initialize a progress container using mpb.New(). You can customize the container width using mpb.WithWidth(). Create the bar using p.New(total, ...) and configure its appearance using mpb.BarStyle() and decorators via mpb.PrependDecorators() and mpb.AppendDecorators(). Finally, call p.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()
    }
  9. Render multiple progress bars concurrently

    master

    To manage multiple bars, you can pass a sync.WaitGroup to mpb.New(mpb.WithWaitGroup(&wg)). This allows p.Wait() to block until both the progress bars are finished and the WaitGroup is done. Use p.AddBar(total, ...) to add new bars to the container. For accurate EWMA (Exponentially Weighted Moving Average) ETA calculations, use bar.EwmaIncrement(duration) instead of the standard Increment() 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()
  10. Manage bar lifecycle and queuing

    master

    Control how bars behave when they complete, abort, or are queued:

    • BarQueueAfter(bar *Bar): Places the current bar in a queue to start after the specified bar completes 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's PopCompletedMode is enabled.
    • BarNoPop(): Disables the bar from
  11. Extend bars with arbitrary lines using BarExtender

    master

    The BarExtender option allows you to extend a bar with arbitrary lines. The provided BarFiller will be called during every render/flush cycle. Any lines written to the underlying io.Writer will extend the bar either above or below the bar itself.

    • BarExtender(filler BarFiller, rev bool):
      • filler: The BarFiller used to generate the lines.
      • rev: If true, lines are added in the
  12. Add progress bars and spinners

    master

    You can add bars to a Progress container 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 custom BarFillerBuilder.
    • MustAdd(total int64, filler BarFiller, options ...BarOption): Same as New, but panics if the container is already closed (after Wait()).
    • Add(total int64, filler BarFiller, options ...BarOption): The base method for adding a bar with a specific BarFiller. Returns (nil, ErrDone) if called after Wait().