Cookbook
Short answers to "how do I ...?". Each recipe shows the code and its real output; the reference has the details. The outputs show the terminal at the end of the run, or while running when the recipe says so.
The smallest bar
program minimal
!< The smallest progress bar: the default range is [0, 1].
use, intrinsic :: iso_fortran_env, only : R8P=>real64
use forbear, only : bar_object
implicit none
type(bar_object) :: bar ! the progress bar
real(R8P) :: x ! progress, in [0, 1]
integer :: i ! counter
integer :: j ! counter
real(R8P) :: y ! work result
x = 0._R8P
call bar%initialize(filled_char_string='+', prefix_string='progress |', suffix_string='|', &
add_progress_percent=.true.)
call bar%start
do i = 1, 20
x = x + 0.05_R8P
do j = 1, 1000000
y = sqrt(x) ! just spend some time
enddo
call bar%update(current=x)
enddo
if (y < 0._R8P) print *, y
endprogram minimal$ minimal
progress |++++++++++++++++++++++++++++++++| 100%The default range is [0, 1]: pass the fraction done.
A bar over the steps of a loop
call bar%initialize(max_value=real(steps, R8P)) ! 1. initialize
call bar%start ! 2. start
do step = 1, steps
call advance ! the real work
call bar%update(current=real(step, R8P)) ! 3. update
enddo
print '(A,I0,A)', 'march: ', steps, ' time steps done'$ march
********************************
march: 50 time steps donemax_value is the number of steps, current the step: both real(real64).
A loop of many iterations
call bar%initialize(prefix_string='records ', add_progress_percent=.true., frequency=5, &
min_value=real(first - 1, R8P), max_value=real(last, R8P))
call bar%start
do i = first, last
call bar%update(current=real(i, R8P))
enddo$ many
records ******************************** 100%The range starts one before the first record, so that the bar starts at 0%. Updating at every iteration is cheap: the bar is drawn at most ten times a second (min_interval), whatever the number of iterations. frequency=5 draws it only at every 5%, which also keeps the log of a batch job to 21 lines.
Fewer drawings, without recompiling
export FORBEAR_MIN_INTERVAL=1 makes every bar of a program wait one second between two drawings on a terminal, unless the program passes min_interval itself; 0 draws at every update. It is the knob for a slow remote terminal, and the docs set it to 0 so that their outputs do not depend on the speed of the machine. See How often the bar is drawn.
A smooth bar
call bar%initialize(prefix_string='march ', bracket_left_string='▕', bracket_right_string='▏', &
partial_blocks=.true., filled_char_color_fg='cyan', &
width=40, max_value=real(steps, R8P))$ march
march ▕██████████████████▍ ▏While running. Eight steps per character. For a visible track, give the empty part a background colour, empty_char_color_bg.
A Unicode bar
call bar%initialize(prefix_string='march ', bracket_left_string='▕', bracket_right_string='▏', &
filled_char_string='█', empty_char_string='░', &
width=40, max_value=real(steps, R8P))$ march
march ▕████████████████████░░░░░░░░░░░░░░░░░░░░▏While running. Save the source as UTF-8; one character for filled_char_string and empty_char_string.
A coloured, solid bar
call bar%initialize(prefix_string='march ', prefix_color_fg='cyan', prefix_style='bold_on', &
bracket_left_string='[', bracket_right_string=']', &
filled_char_string='█', filled_char_color_fg='green', &
empty_char_string='█', empty_char_color_fg='black_intense', &
suffix_string=' time steps', suffix_color_fg='yellow', suffix_style='italics_on', &
width=40, max_value=real(steps, R8P))$ march
march [████████████████████████████████████████] time stepsWhile running. Full blocks in two foreground colours. The colour and style names are in Colours and styles.
Exact colours
call bar%initialize(prefix_string='march ', prefix_color_fg='#89B4FA', width=40, &
filled_char_string='█', filled_char_color_fg='#A6E3A1', &
empty_char_string='█', empty_char_color_fg='#313244', &
add_progress_percent=.true., progress_percent_color_fg='#F9E2AF', max_value=real(steps, R8P))$ march
march ████████████████████████████████████████ 50%While running. #rrggbb in any colour keyword: the same colour on every terminal palette (here Catppuccin Mocha's blue, green, yellow and surface). The terminal must have true colour, as most modern ones do. See 24-bit colours.
A redline: colours by position
call bar%initialize(prefix_string='RPM ', prefix_color_fg='#2EF5C0', prefix_style='bold_on', &
filled_char_string='▌', empty_char_string='▌', empty_char_color_fg='#0E342C', &
bar_zones='0.7:#2EF5C0 0.88:#FFB000 1:#FF3B30', &
add_progress_percent=.true., progress_percent_color_fg='#FFB000', &
width=40, max_value=real(steps, R8P))$ tach
RPM ▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌ 80%While running. Each limit:colour of bar_zones colours the filled cells up to that fraction of the bar; the dark empty cells are the unlit segments of a display. See Zones.
A rising ramp
call bar%initialize(prefix_string='RPM ', prefix_color_fg='#2EF5C0', prefix_style='bold_on', &
bar_profile='ramp', empty_char_color_fg='#0E342C', &
bar_zones='0.7:#2EF5C0 0.88:#FFB000 1:#FF3B30', &
add_progress_percent=.true., progress_percent_color_fg='#FFB000', &
width=40, max_value=real(steps, R8P))$ tach
RPM ▁▁▁▁▁▁▂▂▂▂▂▂▃▃▃▃▃▄▄▄▄▄▄▅▅▅▅▅▆▆▆▆▆▆▇▇▇▇▇█ 80%While running. bar_profile='ramp': blocks rising from one eighth to the full height, lit or unlit; with zones, a tachometer. See Profiles.
Seven-segment numbers
call bar%initialize(prefix_string='march ', prefix_color_fg='#2EF5C0', width=30, &
filled_char_string='▌', filled_char_color_fg='#2EF5C0', &
empty_char_string='▌', empty_char_color_fg='#0E342C', &
add_progress_percent=.true., progress_percent_color_fg='#FFB000', &
add_progress_count=.true., progress_count_color_fg='#FFB000', &
digits='segment', digits_unlit_color='#3A2800', max_value=real(steps, R8P))$ march
march ▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌ 🯸🯸🯵% 🯸🯸🯷/🯱🯲🯰While running. digits='segment' for the percent, count, ETA and elapsed time; digits_unlit_color pads them with unlit 8s. Check that the font of your terminal has these characters (Cascadia Code, Iosevka, JuliaMono do; most defaults do not): see Segment digits.
A dashboard look in one keyword
call vfd%initialize(theme='vfd', prefix_string='vfd ', add_progress_percent=.true., width=30, &
max_value=real(steps, R8P))
call amber%initialize(theme='amber', prefix_string='amber ', bar_profile='ramp', add_progress_percent=.true., &
width=30, max_value=real(steps, R8P), position=1)
call kitt%initialize(theme='kitt', prefix_string='kitt ', indeterminate=.true., add_progress_count=.true., &
width=30, position=2)$ march
vfd ▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌ 58%
amber ▁▁▁▁▁▂▂▂▂▃▃▃▃▄▄▄▄▅▅▅▅▆▆▆▆▇▇▇▇█ 58%
kitt ▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌ 29While running. theme='vfd', 'amber' or 'kitt' sets the segments and the colours of every element; anything passed explicitly wins, and zones, a ramp or segment digits go on top. See Themes.
Percent, count, speed, ETA, scale, times, summary
call bar%initialize(prefix_string='march ', bracket_left_string='[', bracket_right_string=']', &
filled_char_string='#', empty_char_string='.', &
add_progress_percent=.true., progress_percent_color_fg='yellow', &
add_progress_count=.true., progress_count_color_fg='cyan', &
add_progress_speed=.true., progress_speed_color_fg='green', &
add_eta=.true., eta_color_fg='blue', &
add_scale_bar=.true., scale_bar_color_fg='blue', &
add_date_time=.true., date_time_color_fg='magenta', &
add_summary=.true., summary_color_fg='green', &
width=30, max_value=real(steps, R8P))$ march
[ 0.00 (min) (max) 50.00]
march [##############################] 100% 50/50 ( nn.nn%/s) ETA hh:mm:ss
[yyyy/mm/dd hh:mm:ss - yyyy/mm/dd hh:mm:ss]
[done in n.nn s, nn.nn/s]The scale needs width of at least 22 (see Number formats). With a narrower bar the program stops:
program scale_narrow
!< The scale needs a bar at least 22 characters wide.
use forbear, only : bar_object
implicit none
type(bar_object) :: bar ! the progress bar
call bar%initialize(width=20, add_scale_bar=.true.)
endprogram scale_narrow$ scale_narrow
ERROR STOP error: for adding scale bar the bar width must be at least 22 chars
[exit status 1]The speed of now, not the average
call bar%initialize(add_progress_speed=.true., add_eta=.true., smoothing=1._R8P, max_value=real(steps, R8P)) ! momentaryThe speed and the ETA follow an exponential moving average, with weight smoothing (0.3) on the last speed: 1 shows the momentary speed, 0 the average since the start. See update.
A prefix that names the phase
call bar%start
do step = 1, steps
if (step == 10) bar%prefix%string = 'assemble '
if (step == 30) bar%prefix%string = 'solve '
call advance
call bar%update(current=real(step, R8P))
enddo$ march
solve [***********************-------] 78%While running, in the third phase. Assign bar%prefix%string (or bar%suffix%string) while the bar runs: the next drawing shows it, in the colours of initialize. Keep the phases the same width, or the line jumps.
A spinner next to the bar
call bar%initialize(prefix_string='march ', bracket_left_string='[', bracket_right_string='] ', &
filled_char_string='#', empty_char_string='.', &
spinner_string='⠋', spinner_color_fg='red', &
width=40, max_value=real(steps, R8P))$ march
march [####################....................] ⠴While running. Pick the key of a spinner from the catalogue.
A spinner alone
call bar%initialize(prefix_string='march ', spinner_string='⠋', spinner_color_fg='red', &
width=0, max_value=real(steps, R8P))$ march
march [########################################] ⠋
march ⠴While running. width=0: no bar, only the prefix and the spinner.
A percentage alone
call bar%initialize(prefix_string='march ', add_progress_percent=.true., &
width=0, max_value=real(steps, R8P))$ march
march [########################################] ⠋
march ⠋
march 50%Lines above the bar, a message at its end
call bar%start
residual = 1._R8P
do step = 1, steps
call advance
residual = residual / 2._R8P
if (mod(step, 10) == 0) then
write(text, '(A,I0,A)') 'step ', step, ': solution saved'
call bar%write(trim(text)) ! a line above the bar
endif
write(text, '(A,ES9.2)') 'residual', residual
call bar%update(current=real(step, R8P), message=trim(text)) ! the end of the bar line
enddo$ march
step 10: solution saved
step 20: solution saved
step 30: solution saved
step 40: solution saved
step 50: solution saved
march [##############################] 100% residual 8.88E-16bar%write prints above the running bar; message= shows the text at the end of the bar line until the next one. A plain print would be drawn over the bar.
Output of a library while the bar runs
call bar%start
do step = 1, steps
call advance
call bar%update(current=real(step, R8P))
if (mod(step, 20) == 0) then
call bar%suspend ! the bar leaves the terminal...
call save_solution(step) ! ...to a library that prints on its own...
call bar%resume ! ...and comes back below its lines
endif
enddo$ march
io_library: writing solution_0020.h5
io_library: done, 2.1 MB
io_library: writing solution_0040.h5
io_library: done, 2.1 MB
march [##############################] 100%suspend clears the bar and frees the terminal, resume draws it again below what was printed: see suspend and resume.
A bar for each loop of a nest
call steps_bar%initialize(prefix_string='time step ', bracket_left_string='[', bracket_right_string=']', &
partial_blocks=.true., filled_char_color_fg='cyan', add_progress_count=.true., &
width=30, max_value=real(steps, R8P))
call iterations_bar%initialize(prefix_string=' newton ', bracket_left_string='[', bracket_right_string=']', &
partial_blocks=.true., filled_char_color_fg='yellow', add_progress_count=.true., &
width=30, max_value=real(iterations, R8P), position=1)call steps_bar%start
do step = 1, steps
call iterations_bar%start
do iteration = 1, iterations
call advance
call iterations_bar%update(current=real(iteration, R8P))
enddo
call steps_bar%update(current=real(step, R8P))
enddo
print '(A)', 'march: done'$ march
time step [████████████ ] 2/5
newton [██████████████████ ] 6/10While running. The inner bar, at position=1, is cleared when it completes and drawn again by its next start.
The bar on standard error
call bar%initialize(prefix_string='march ', bracket_left_string='[', bracket_right_string=']', &
filled_char_string='#', empty_char_string='.', add_progress_percent=.true., &
frequency=10, output_unit=error_unit, &
width=40, max_value=real(steps, R8P))Standard output keeps only the results of the program,
$ stderr 2> /dev/null
step 10, residual 9.77E-04
step 20, residual 9.54E-07
step 30, residual 9.31E-10
step 40, residual 9.09E-13
step 50, residual 8.88E-16and standard error only the bar:
$ stderr > results.txt
march [########################################] 100%A clean log in a batch job
Nothing to do: when the output of the bar is not a terminal, the bar writes a plain line every 10%.
$ march > run.log; cat run.log
march [..............................] 0% 0/50 ETA --:--:--
march [###...........................] 10% 5/50 ETA hh:mm:ss
march [######........................] 20% 10/50 ETA hh:mm:ss
march [#########.....................] 30% 15/50 ETA hh:mm:ss
march [############..................] 40% 20/50 ETA hh:mm:ss
march [###############...............] 50% 25/50 ETA hh:mm:ss
march [##################............] 60% 30/50 ETA hh:mm:ss
march [#####################.........] 70% 35/50 ETA hh:mm:ss
march [########################......] 80% 40/50 ETA hh:mm:ss
march [###########################...] 90% 45/50 ETA hh:mm:ss
march [##############################] 100% 50/50 ETA hh:mm:ss
[done in n.nn s, nn.nn/s]
march: doneinteractive=.false. (or FORBEAR_INTERACTIVE=0) forces this mode on a terminal too; see Terminals and logs.
A log line every ten minutes
call bar%initialize(max_value=real(steps, R8P), log_interval=600._R8P)A solver of unknown length, logging a line every so often (here every few iterations, from chapter 8):
$ march > run.log; cat run.log
solve 0 iterations, hh:mm:ss elapsed
solve 2 iterations, hh:mm:ss elapsed
solve 4 iterations, hh:mm:ss elapsed
solve 6 iterations, hh:mm:ss elapsed
solve 8 iterations, hh:mm:ss elapsed
solve 10 iterations, hh:mm:ss elapsedOr, for every bar, without recompiling: export FORBEAR_LOG_INTERVAL=600 in the job script. See Signs of life in long jobs.
No bars at all
Turn every bar of a program off from its environment, without recompiling; write still prints:
$ FORBEAR_DISABLE=1 march
march: doneIn code, disabled=.true.; under MPI, draw the bar of one process only:
call bar%initialize(max_value=real(steps, R8P), disabled=(rank /= 0)) ! rank from MPI_Comm_rankA layout of your own
call bar%initialize(template='{prefix} {percent:yellow} [{bar:20}] {count} files, {eta:blue} left', &
prefix_string='load', max_value=real(n, R8P))$ layout
load 50% [**********----------] 6/12 files, hh:mm:ss leftWhile running. Any order, any words between the fields: see Layout templates. At the end:
$ layout
load 100% [********************] 12/12 files, hh:mm:ss leftA field of your own
module residual_fields
!< A field of the program: the residual of the solution, read at every drawing.
use, intrinsic :: iso_fortran_env, only : R8P=>real64
use forbear, only : field_object, progress_object
implicit none
private
public :: residual_field
type, extends(field_object) :: residual_field
real(R8P), pointer :: value => null() ! the residual, a variable of the program
contains
procedure, pass(self) :: render
endtype residual_field
contains
function render(self, progress) result(text)
!< Return the residual, always 7 characters wide.
class(residual_field), intent(in) :: self
type(progress_object), intent(in) :: progress
character(len=:), allocatable :: text
character(len=7) :: buffer
write(buffer, '(ES7.1)') self%value
text = buffer
endfunction render
endmodule residual_fieldscall bar%initialize(template='march {bar:30} {percent:yellow} step {count:cyan} ETA {eta:blue} res {residual:magenta}', &
partial_blocks=.true., filled_char_color_fg='cyan', empty_char_color_bg='black_intense', &
max_value=real(steps, R8P))
field%value => residual
call bar%add_field('residual', field) ! after initialize, before start$ march
march ██████████████████████████████ 100% step 50/50 ETA hh:mm:ss res 8.9E-16The field reads the residual of the program through a pointer, at every drawing.
A loop of unknown length
call bar%initialize(template='solve {spinner:cyan} [{bar:20}] {count:yellow} iterations, {elapsed:blue}', &
indeterminate=.true., spinner_string='⠋', filled_char_string='#', empty_char_string='.')
residual = 1._R8P
iterations = 0
call bar%start
do while (residual > 1.e-6_R8P) ! how many iterations: unknown until done
call advance
residual = residual / 2._R8P
iterations = iterations + 1
call bar%update(current=real(iterations, R8P))
enddo
call bar%finish$ march
solve ⠇ [........#####.......] 8 iterations, hh:mm:ssWhile running. current counts the iterations; finish ends the bar. No percent, ETA or scale: see chapter 10.
A scanner for a loop of unknown length
call bar%initialize(prefix_string='solve ', prefix_color_fg='#FF3B30', &
indeterminate=.true., width=24, add_progress_count=.true., progress_count_color_fg='#FFB000', &
filled_char_string='▬', filled_char_color_fg='#FF3B30', &
empty_char_string='▬', empty_char_color_fg='#3C0C0A', &
pulse_trail='#C0281E #7A1912 #4A0F0B')$ solve
solve ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 31While running. pulse_trail turns the pulse of an indeterminate bar into a one-cell head with a fading trail; with #rrggbb shades, as smooth as you like. See Pulse trail.
Leaving a loop early
steps = 50
call bar%initialize(prefix_string='march ', bracket_left_string='[', bracket_right_string=']', &
filled_char_string='#', empty_char_string='.', add_progress_percent=.true., &
progress_percent_color_fg='yellow', message_color_fg='green', width=30, &
max_value=real(steps, R8P))
residual = 1._R8P
call bar%start
do step = 1, steps
call advance
residual = residual / 2._R8P
call bar%update(current=real(step, R8P))
if (residual < 1.e-9_R8P) exit ! steady state: the remaining steps are not needed
enddo
call bar%finish(message='steady state')finish ends the bar where the loop left it: the line ended, the cursor shown, the summary written if asked.
Several bars one after another
do p = 1, size(phases)
call bar%initialize(prefix_string=phases(p), bracket_left_string='[', bracket_right_string=']', &
filled_char_string='=', empty_char_string=' ', add_progress_percent=.true., width=30)
call run_phase
enddo$ sequence
mesh ********************************
solve ********************************
write ********************************
mesh [==============================] 100%
solve [==============================] 100%
write [==============================] 100%Every initialize resets the bar, so pass every setting each time. The first three lines of the output come from the other loop of the program, which copies a configured bar (bar = style) and then calls initialize to change the prefix: the copy is lost, and those bars have the defaults.
call style%initialize(bracket_left_string='[', bracket_right_string=']', filled_char_string='=', &
empty_char_string=' ', add_progress_percent=.true., width=30)
do p = 1, size(phases)
bar = style ! copy the configuration
call bar%initialize(prefix_string=phases(p)) ! re-initialize: everything else is reset!
call run_phase
enddobar%destroy frees a bar and resets every default, as initialize does first.
Two bars running at the same time need their own lines: see A bar for each loop of a nest.
A cursor that stays visible
call bar%initialize(hide_cursor=.false., max_value=real(steps, R8P)) ! the cursor stays visible, even if the program stopsOn a terminal the bar hides the cursor while it runs and shows it again at 100%. If the program may stop before (an error stop, a crash, Ctrl-C), hide_cursor=.false. leaves it visible. See If the program stops.
Printing only when no bar is running
call bar%start
do step = 1, steps
call bar%update(current=real(step, R8P))
if (.not.bar%is_stdout_locked()) print '(A)', 'the bar is done: the terminal is free again'
enddois_stdout_locked is true while a bar runs on a terminal: anything printed then would be drawn over the bar (use bar%write instead). Once the bar completes, the terminal is free. See is_stdout_locked.