ProgressMeter.jl

repository·master·Indexed 21 days ago

https://github.com/timholy/progressmeter.jl

A Julia package providing progress bars and meters for tasks with known step counts, target thresholds, or unknown iteration counts. It features the @showprogress macro for loops, maps, and distributed computations, as well as manual control via the Progress, ProgressThresh, and ProgressUnknown types. Supports customization of glyphs, colors, and output redirection, with specific behavior configurations for Jupyter Notebooks.

Tokens
3K
Snippets
11
Records
12
Agent score
24%

What's inside ProgressMeter.jl

  1. Use the @showprogress macro for loops and maps

    master

    The @showprogress macro is a convenient way to wrap loops or functional programming constructs to display a progress bar. It supports for loops, comprehensions, @distributed loops, and map/pmap/reduce calls, provided the iterable implements length.

    If the computation is too fast to require updates, no output is displayed. For @distributed loops without a reducer, an @sync is implied.

    using Distributed
    using ProgressMeter
    
    # For loops
    @showprogress dt=1 desc="Computing..." for i in 1:50
        sleep(0.1)
    end
    
    # pmap
    @showprogress pmap(1:10) do x
        sleep(0.1)
        x^2
    end
    
    # reduce
    @showprogress reduce(1:10) do x, y
        sleep(0.1)
        x + y
    end
    
    # Distributed loops
    @showprogress @distributed for i in 1:10
        sleep(0.1)
    end
    
    # Distributed loops with reducer
    result = @showprogress desc="Computing..." @distributed (+) for i in 1:10
        sleep(0.1)
        i^2
    end
  2. Redirect progress meter output to another terminal

    master

    When developing or debugging, you can redirect the progress meter output to a different terminal window to prevent it from interfering with your primary Julia REPL.

    1. In the target terminal window (where you want the progress bar to appear), run the tty command to find its device path (e.g., /dev/pts/3).
    2. In your Julia REPL, open that path for writing.
    3. Wrap the IO object in an IOContext with :color => true to ensure color support is maintained.
    4. Pass this IOContext to the output keyword argument of the Progress, ProgressThresh, or ProgressUnknown constructors.
    # 1. Get the path from the other terminal using `tty"
    # 2. In Julia:
    io = open("/dev/pts/3", "w")
    # 3. Wrap in IOContext for color support
    # 4. Pass to constructor
    # 
    # Note: Replace "/dev/pts/3" with your actual tty path
    # 
    # ioc = IOContext(io, :color => true)
    # prog = Progress(10; output = ioc)
  3. Manually control progress with Progress() and update!()

    master

    For more granular control, or when the progress increment is not constant (e.g., reading files where progress depends on byte position), use the Progress type and its update methods.

    • next!(p): Increments the progress by one step.
    • update!(p, value): Sets the progress to a specific value (useful for non-monotonic increments or variable step sizes).
    using ProgressMeter
    
    # Standard manual increment
    function my_long_running_function(filenames::Array)
        n = length(filenames)
        p = Progress(n; dt=1.0)
        for f in filenames
            next!(p)
        end
    end
    
    # Variable increment (e.g., file reading)
    function readFileLines(fileName::String)
        file = open(fileName,"r")
        seekend(file)
        fileSize = position(file)
        seekstart(file)
    
        p = Progress(fileSize; dt=1.0)
        while !eof(file)
            line = readline(file)
            update!(p, position(file))
        end
    end
  4. Customize progress bar appearance and glyphs

    master

    You can customize the visual style of the progress bar using several keyword arguments in the Progress constructor:

    • desc: A string prepended to the output.
    • barlen: The length of the progress bar in characters.
    • color: The color of the bar (e.g., :yellow).
    • barglyphs: A BarGlyphs object defining the characters used for the bar.

    BarGlyphs can be initialized with a 5-character string or 5 individual characters. You can also provide a vector of characters to create smooth transitions between empty and filled states.

    using ProgressMeter
    
    # Basic description
    p = Progress(n; desc="Computing initial pass...")
    
    # Custom glyphs, length, and color
    p = Progress(n; dt=0.5, barglyphs=BarGlyphs("[=> ]"), barlen=50, color=:yellow)
    
    # Smooth transition glyphs
    p = Progress(n; dt=0.5,
                 barglyphs=BarGlyphs('|','█', [' ' ,'▂' ,'▃' ,'▄' ,'▅' ,'▆', '▇'],' ','|',),
                 barlen=10)
  5. Implement progress for custom map-like functions

    master

    You can extend @showprogress support to custom functions (like tmap from ThreadTools.jl) by defining ProgressMeter.ncalls. This tells the macro how to extract the iteration count from the function call.

    using ThreadTools, ProgressMeter
    
    # Define how to find the number of calls for tmap
    ProgressMeter.ncalls(::typeof(tmap), ::Function, args...) = ProgressMeter.ncalls_map(args...)
    ProgressMeter.ncalls(::typeof(tmap), ::Function, ::Int, args...) = ProgressMeter.ncalls_map(args...)
    
    # Now @showprogress works with tmap
    @showprogress tmap(abs2, 1:10^5)
  6. Use ProgressUnknown for non-deterministic tasks

    master

    For tasks where the total number of steps is unknown (e.g., reading until a specific pattern is found), use ProgressUnknown.

    Key features:

    • next!(p): Increments the counter.
    • update!(p, value): Sets the counter manually.
    • finish!(p): Ends the progress meter. By default, it changes the spinner to , but you can pass a custom character like finish!(p, spinner='✗') on failure.
    • spinner=true: Enables a spinning animation.
    • Custom spinners can be passed to next!(p, spinner="...") to change the animation frame.
    using ProgressMeter
    
    # Basic unknown progress
    prog = ProgressUnknown(desc="Titles read:")
    for val in ["a" , "b", "c", "d"]
        next!(prog)
        if val == "c"
            finish!(prog)
            break
        end
    end
    
    # Using a spinner
    prog = ProgressUnknown(desc="Working hard:", spinner=true)
    while true
        next!(prog)
        rand(1:2*10^8) == 1 && break
    end
    finish!(prog)
    
    # Customizing spinner animation
    prog = ProgressUnknown(desc="Burning the midnight oil:", spinner=true)
    while true
        next!(prog, spinner="🌑🌒🌓🌔🌕🌖🌗🌘")
        rand(1:10^8) == 0xB00 && break
    end
    finish!(prog)
  7. Disable progress meters conditionally

    master

    You can disable a progress meter using the enabled keyword argument. This is useful for:

    1. Disabling output in CI/CD environments.
    2. Preventing nested progress bars (e.g., disabling an inner loop's progress bar when an outer loop is already tracking progress).
    using ProgressMeter
    
    # Disable via environment variable
    const SHOW_PROGRESS_BARS = parse(Bool, get(ENV, "PROGRESS_BARS", "true"))
    
    # Disable inner loop to avoid clutter
    for i in 1:m
        my_awesome_slow_loop(i; show_progress=false)
        next!(p)
    end
    
    # Automatically disable in non-TTY or CI environments
    function is_logging(io)
        return !isa(io, Base.TTY) || (get(ENV, "CI", nothing) == "true")
    end
    
    p = Progress(n; output = stderr, enabled = !is_logging(stderr))
  8. Use ProgressThresh for convergence-based tasks

    master

    When a task terminates based on a target threshold (e.g., an optimization algorithm reaching a specific tolerance) rather than a fixed number of steps, use ProgressThresh(threshold).

    using ProgressMeter
    
    prog = ProgressThresh(1e-5; desc="Minimizing:")
    for val in exp10.(range(2, stop=-6, length=20))
        update!(prog, val)
        sleep(0.1)
    end
  9. Show average speed per iteration

    master

    To include the average time per iteration (e.g., 12.34 s/it) in the output, set showspeed=true in the constructor of Progress, ProgressUnknown, or ProgressThresh.

    using ProgressMeter
    
    x, n = 1, 10
    p = Progress(n; showspeed=true)
    for iter in 1:10
        x *= 2
        sleep(0.5)
        next!(p)
    end
  10. Handle progress in Jupyter Notebooks

    master

    Jupyter notebooks/lab handle cell output differently than standard terminals. To control whether progress bars append to the output or clear the cell output, use ProgressMeter.ijulia_behavior():

    • ProgressMeter.ijulia_behavior(:append): Restores the behavior of printing progress bars repeatedly (standard behavior in older versions).
    • ProgressMeter.ijulia_behavior(:clear): Clears the cell output (default in newer versions).
  11. Display additional information with showvalues

    master

    You can display metadata (like iteration counts or current error values) below the progress bar using the showvalues keyword in next!().

    Performance Tip: To avoid evaluating the metadata every time the loop runs (even if the progress bar isn't being updated to the screen), pass a zero-argument function as a callback. This ensures the values are only computed when necessary.

    using ProgressMeter
    
    x, n = 1, 10
    p = Progress(n)
    
    # Method 1: Direct values (evaluated every iteration)
    for iter in 1:10
        x *= 2
        next!(p; showvalues = [("iteration count", iter), ("x", x)])
    end
    
    # Method 2: Callback function (evaluated only when necessary)
    generate_showvalues(iter, x) = () -> [("iteration count", iter), ("x", x)]
    for iter in 1:10
        x *= 2
        next!(p; showvalues = generate_showvalues(iter, x))
    end