2020-07-04 21:00:11 +00:00
|
|
|
package progress
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"github.com/logrusorgru/aurora"
|
|
|
|
"github.com/vbauerster/mpb/v5"
|
|
|
|
"github.com/vbauerster/mpb/v5/decor"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Encapsulates progress tracking.
|
2020-07-04 21:00:11 +00:00
|
|
|
type Progress struct {
|
2020-07-11 20:57:44 +00:00
|
|
|
progress *mpb.Progress
|
|
|
|
barTemplate *Bar
|
|
|
|
barGlobal *Bar
|
|
|
|
|
2020-07-09 19:20:00 +00:00
|
|
|
captureData *captureData
|
|
|
|
stdCaptureMutex *sync.Mutex
|
|
|
|
stdout *strings.Builder
|
|
|
|
stderr *strings.Builder
|
2020-07-12 16:09:29 +00:00
|
|
|
|
|
|
|
colorizer aurora.Aurora
|
2020-07-04 21:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Creates and returns a new progress tracking object.
|
2020-07-12 16:09:29 +00:00
|
|
|
func NewProgress(noColor bool) *Progress {
|
2020-07-04 21:00:11 +00:00
|
|
|
p := &Progress{
|
|
|
|
progress: mpb.New(
|
|
|
|
mpb.WithOutput(os.Stderr),
|
|
|
|
mpb.PopCompletedMode(),
|
|
|
|
),
|
2020-07-09 19:20:00 +00:00
|
|
|
stdCaptureMutex: &sync.Mutex{},
|
|
|
|
stdout: &strings.Builder{},
|
|
|
|
stderr: &strings.Builder{},
|
2020-07-12 16:09:29 +00:00
|
|
|
colorizer: aurora.NewAurora(!noColor),
|
2020-07-04 21:00:11 +00:00
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Creates and returns a progress bar that tracks request progress for a specific template.
|
|
|
|
func (p *Progress) SetupTemplateProgressbar(templateIndex int, templateCount int, name string, requestCount int64) {
|
2020-07-12 16:09:29 +00:00
|
|
|
color := p.colorizer
|
|
|
|
barName := "[" + color.Green(name).String() + "]"
|
2020-07-05 21:38:58 +00:00
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
if templateIndex > -1 && templateCount > -1 {
|
2020-07-12 16:09:29 +00:00
|
|
|
barName = color.Sprintf("[%d/%d] ",
|
|
|
|
color.Bold(color.Cyan(templateIndex)),
|
|
|
|
color.Cyan(templateCount)) + barName
|
2020-07-11 20:57:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bar := p.setupProgressbar(barName, requestCount)
|
|
|
|
|
|
|
|
if p.barTemplate != nil {
|
2020-07-11 21:52:45 +00:00
|
|
|
// ensure any previous bar has finished and dropped requests have also been considered
|
2020-07-11 20:57:44 +00:00
|
|
|
p.barTemplate.finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
p.barTemplate = &Bar{
|
|
|
|
bar: bar,
|
|
|
|
total: requestCount,
|
|
|
|
initialTotal: requestCount,
|
|
|
|
}
|
2020-07-05 21:38:58 +00:00
|
|
|
}
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Creates and returns a progress bar that tracks all the requests progress.
|
|
|
|
// This is only useful when multiple templates are processed within the same run.
|
|
|
|
func (p *Progress) SetupGlobalProgressbar(hostCount int64, templateCount int, requestCount int64) {
|
2020-07-12 16:09:29 +00:00
|
|
|
color := p.colorizer
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
hostPlural := "host"
|
|
|
|
if hostCount > 1 {
|
|
|
|
hostPlural = "hosts"
|
|
|
|
}
|
|
|
|
|
2020-07-12 16:09:29 +00:00
|
|
|
barName := "[" + color.Sprintf(
|
|
|
|
color.Cyan("%d templates, %d %s"),
|
|
|
|
color.Bold(color.Cyan(templateCount)),
|
|
|
|
color.Bold(color.Cyan(hostCount)),
|
2020-07-11 20:57:44 +00:00
|
|
|
hostPlural) + "]"
|
|
|
|
|
|
|
|
bar := p.setupProgressbar(barName, requestCount)
|
|
|
|
|
|
|
|
p.barGlobal = &Bar{
|
|
|
|
bar: bar,
|
|
|
|
total: requestCount,
|
|
|
|
initialTotal: requestCount,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update progress tracking information and increments the request counter by one unit.
|
|
|
|
// If a global progress bar is present it will be updated as well.
|
2020-07-05 21:38:58 +00:00
|
|
|
func (p *Progress) Update() {
|
2020-07-11 20:57:44 +00:00
|
|
|
p.barTemplate.bar.Increment()
|
|
|
|
|
|
|
|
if p.barGlobal != nil {
|
|
|
|
p.barGlobal.bar.Increment()
|
|
|
|
}
|
2020-07-04 21:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Drops the specified number of requests from the progress bar total.
|
|
|
|
// This may be the case when uncompleted requests are encountered and shouldn't be part of the total count.
|
|
|
|
// If a global progress bar is present it will be updated as well.
|
|
|
|
func (p *Progress) Drop(count int64) {
|
|
|
|
p.barTemplate.Drop(count)
|
|
|
|
|
|
|
|
if p.barGlobal != nil {
|
|
|
|
p.barGlobal.Drop(count)
|
|
|
|
}
|
2020-07-05 22:09:58 +00:00
|
|
|
}
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Ensures that a progress bar's total count is up-to-date if during an enumeration there were uncompleted requests and
|
|
|
|
// wait for all the progress bars to finish.
|
|
|
|
// If a global progress bar is present it will be updated as well.
|
2020-07-04 21:00:11 +00:00
|
|
|
func (p *Progress) Wait() {
|
2020-07-11 20:57:44 +00:00
|
|
|
p.barTemplate.finish()
|
|
|
|
|
|
|
|
if p.barGlobal != nil {
|
|
|
|
p.barGlobal.finish()
|
2020-07-07 20:39:43 +00:00
|
|
|
}
|
2020-07-11 20:57:44 +00:00
|
|
|
|
2020-07-04 21:00:11 +00:00
|
|
|
p.progress.Wait()
|
|
|
|
}
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Creates and returns a progress bar.
|
|
|
|
func (p *Progress) setupProgressbar(name string, total int64) *mpb.Bar {
|
2020-07-12 16:09:29 +00:00
|
|
|
color := p.colorizer
|
2020-07-11 20:57:44 +00:00
|
|
|
return p.progress.AddBar(
|
|
|
|
total,
|
|
|
|
mpb.BarNoPop(),
|
|
|
|
mpb.BarRemoveOnComplete(),
|
|
|
|
mpb.PrependDecorators(
|
|
|
|
decor.Name(name, decor.WCSyncSpaceR),
|
2020-07-12 16:09:29 +00:00
|
|
|
decor.CountersNoUnit(color.Blue(" %d/%d").String(), decor.WCSyncSpace),
|
|
|
|
decor.NewPercentage(color.Bold("%d").String(), decor.WCSyncSpace),
|
2020-07-11 20:57:44 +00:00
|
|
|
),
|
|
|
|
mpb.AppendDecorators(
|
2020-07-12 16:09:29 +00:00
|
|
|
decor.AverageSpeed(0, color.Yellow("%.2f r/s ").String(), decor.WCSyncSpace),
|
|
|
|
decor.Elapsed(decor.ET_STYLE_GO, decor.WCSyncSpace),
|
2020-07-11 20:57:44 +00:00
|
|
|
decor.AverageETA(decor.ET_STYLE_GO, decor.WCSyncSpace),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
2020-07-04 21:00:11 +00:00
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Starts capturing stdout and stderr instead of producing visual output that may interfere with the progress bars.
|
2020-07-04 21:00:11 +00:00
|
|
|
func (p *Progress) StartStdCapture() {
|
2020-07-09 19:20:00 +00:00
|
|
|
p.stdCaptureMutex.Lock()
|
2020-07-04 21:00:11 +00:00
|
|
|
p.captureData = startStdCapture()
|
|
|
|
}
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Stops capturing stdout and stderr and store both output to be shown later.
|
2020-07-09 18:57:24 +00:00
|
|
|
func (p *Progress) StopStdCapture() {
|
2020-07-04 21:00:11 +00:00
|
|
|
stopStdCapture(p.captureData)
|
2020-07-09 18:57:24 +00:00
|
|
|
p.stdout.Write(p.captureData.DataStdOut.Bytes())
|
|
|
|
p.stderr.Write(p.captureData.DataStdErr.Bytes())
|
2020-07-09 19:20:00 +00:00
|
|
|
p.stdCaptureMutex.Unlock()
|
2020-07-09 18:57:24 +00:00
|
|
|
}
|
2020-07-08 21:13:53 +00:00
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Writes the captured stdout data to stdout, if any.
|
2020-07-09 18:57:24 +00:00
|
|
|
func (p *Progress) ShowStdOut() {
|
|
|
|
if p.stdout.Len() > 0 {
|
|
|
|
fmt.Fprint(os.Stdout, p.stdout.String())
|
2020-07-04 21:00:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-11 20:57:44 +00:00
|
|
|
// Writes the captured stderr data to stderr, if any.
|
2020-07-09 18:57:24 +00:00
|
|
|
func (p *Progress) ShowStdErr() {
|
|
|
|
if p.stderr.Len() > 0 {
|
|
|
|
fmt.Fprint(os.Stderr, p.stderr.String())
|
|
|
|
}
|
2020-07-04 21:00:11 +00:00
|
|
|
}
|