A simple example on implementing progress bar in GoLang

  sonic0002        2020-08-08 23:53:15       22,602        5         

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

TUTORIAL  EXAMPLE  GOLANG  PROGRESS BAR 

       

  RELATED


  5 COMMENTS


Anonymous [Reply]@ 2021-01-15 00:18:39

There is

Anonymous [Reply]@ 2021-01-15 00:31:27

There is a bug in this example. The bug is triggered because of the buggy bar.rate calculation in Bar.Play() function. To reproduce this, try:

func main(){
 var bar progressbar Bar
 bar.NewOption(0, 100)
 for i:= 1; i<=4; i++{
   time.Sleep(20*time.Millisecond)
   bar.Play(int64(i*25))
 }
 bar.Finish()
}

The fix.

func (bar *Bar) Play(cur int64) {

        bar.cur = cur

        last := bar.percent

        bar.percent = bar.getPercent()

        if bar.percent != last {

                var i int64 = 0

                for ; i < bar.percent - last; i++ {

                        bar.rate += bar.graph

                }

                fmt.Printf("\r[%-50s]%3d%% %8d/%d", bar.rate, bar.percent*2, bar

.cur, bar.total)

        }

}

 

Anonymous [Reply]@ 2021-01-15 00:37:26

Also, update the getPercent().

func (bar *Bar) getPercent() int64 {
   return int64((float32(bar.cur) / float32(bar.total))*50)
}
Anonymous [Reply]@ 2021-07-12 03:44:43

I get an error while trying to run this code because the progressbar.Bar in the main function. Progressbar is not defined anywhere in the code. Can someone help me with this?

Ke Pi [Reply]@ 2021-07-13 00:03:53

Have updated the full code snippet, can check at the bottom of the article. Thx



  RANDOM FUN

Destroy cascading