Sometimes when handling a long running process, there would be need to track the progress so that people know something is still running instead of doubting something goes wrong. In this case, a progress bar which indicates the current status and progress would be desired.
This post will show an example on how to implement progress bar using GoLang. Let's take a look at the final outcome first before jumping into the implementation detail.
The key in the implementation is actually just the \r
control flag for fmt.Printf()
. \r is actually the carriage return control which tells the cursor to move back to the beginning of the line and starts to print which basically simulates a replace and write function in this progress bar case.
Alright here comes the implementation. First let's define the struct for the progress bar.
type Bar struct {
percent int64 // progress percentage
cur int64 // current progress
total int64 // total value for progress
rate string // the actual progress bar to be printed
graph string // the fill value for progress bar
}
Here cur
and total
basically are used to control the progress bar status. percent
can be calculated with these two values. Also graph
can be any character like #, @, = etc to indicate what character to be used to fill the progress bar.
Next comes with the initialization. Two functions are provided: NewOption()
and NewOptionWithGraph()
. NewOption()
is to initialize some of the values such as graph and start position.
func (bar *Bar) NewOption(start, total int64) {
bar.cur = start
bar.total = total
if bar.graph == "" {
bar.graph = "â–ˆ"
}
bar.percent = bar.getPercent()
for i := 0; i < int(bar.percent); i += 2 {
bar.rate += bar.graph // initial progress position
}
}
This function takes two parameters: start
and total
. total
is the total maximum task progress. start
tells where the initial progress position is, it may not be 0. In cases like consecutive upload post interruption, the initial progress can be set to the last progress value before the interruption.
One more note to take here is the step length is 2 instead of 1. This is because the progress value is from 0 to 100 while the max number of characters to display the progress bar is 50 in this example. If you use 100 characters to display the whole progress bar, the step length will be 1.
getPercent()
is simple function to calculate the percentage based on cur
and total
.
func (bar *Bar) getPercent() int64 {
return int64(float32(bar.cur) / float32(bar.total) * 100)
}
NewOptionWithGraph()
is similar to NewOption()
with only one additional graph
parameter which allows you to set the fill sign.
func (bar *Bar) NewOptionWithGraph(start, total int64, graph string) {
bar.graph = graph
bar.NewOption(start, total)
}
Next is the most important function of the whole implementation. After having the progress bar struct created, there needs a way to display the dynamic changing progress on the console.
func (bar *Bar) Play(cur int64) {
bar.cur = cur
last := bar.percent
bar.percent = bar.getPercent()
if bar.percent != last && bar.percent%2 == 0 {
bar.rate += bar.graph
}
fmt.Printf("\r[%-50s]%3d%% %8d/%d", bar.rate, bar.percent, bar.cur, bar.total)
}
In above code snippet, the most important statement is the last one. It is to print the updated progress at the beginning of the line with the newly updated rate, percentage.
After displaying the progress bar in above function, there is no newline printed. Hence a final function Finish()
is needed.
func (bar *Bar) Finish(){
fmt.Println()
}
Below is the code to run the example.
func main(){
var bar progressbar.Bar
bar.NewOption(0, 100)
for i:= 0; i<=100; i++{
time.Sleep(100*time.Millisecond)
bar.Play(int64(i))
}
bar.Finish()
}
The complete code snippet would be:
package main
import (
"fmt"
"time"
)
// Bar ...
type Bar struct {
percent int64 // progress percentage
cur int64 // current progress
total int64 // total value for progress
rate string // the actual progress bar to be printed
graph string // the fill value for progress bar
}
func (bar *Bar) NewOption(start, total int64) {
bar.cur = start
bar.total = total
if bar.graph == "" {
bar.graph = "â–ˆ"
}
bar.percent = bar.getPercent()
for i := 0; i < int(bar.percent); i += 2 {
bar.rate += bar.graph // initial progress position
}
}
func (bar *Bar) getPercent() int64 {
return int64((float32(bar.cur) / float32(bar.total)) * 100)
}
func (bar *Bar) Play(cur int64) {
bar.cur = cur
last := bar.percent
bar.percent = bar.getPercent()
if bar.percent != last && bar.percent%2 == 0 {
bar.rate += bar.graph
}
fmt.Printf("\r[%-50s]%3d%% %8d/%d", bar.rate, bar.percent, bar.cur, bar.total)
}
func (bar *Bar) Finish() {
fmt.Println()
}
func main() {
var bar Bar
bar.NewOption(0, 100)
for i := 0; i <= 100; i++ {
time.Sleep(100 * time.Millisecond)
bar.Play(int64(i))
}
bar.Finish()
}
Reference: https://segmentfault.com/a/1190000023375330
There is