Skip to content

Commit

Permalink
progress: buffer output to strings.Builder before writing it out (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t authored May 20, 2018
1 parent 3734d9f commit 844e6f1
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 82 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ The unit-tests for each of the above show how these are to be used.

Partial output of `make bench`:
```
BenchmarkList_Render-8 1000000 1848 ns/op 808 B/op 22 allocs/op
BenchmarkProgress_Render-8 2 800904500 ns/op 8832 B/op 373 allocs/op
BenchmarkTable_Render-8 100000 21025 ns/op 5538 B/op 188 allocs/op
BenchmarkTable_RenderCSV-8 300000 4507 ns/op 2464 B/op 45 allocs/op
BenchmarkTable_RenderHTML-8 200000 6471 ns/op 3921 B/op 44 allocs/op
BenchmarkTable_RenderMarkdown-8 300000 4720 ns/op 2400 B/op 43 allocs/op
BenchmarkList_Render-8 1000000 1845 ns/op 808 B/op 22 allocs/op
BenchmarkProgress_Render-8 2 801518500 ns/op 7492 B/op 211 allocs/op
BenchmarkTable_Render-8 100000 20641 ns/op 5538 B/op 188 allocs/op
BenchmarkTable_RenderCSV-8 300000 4448 ns/op 2464 B/op 45 allocs/op
BenchmarkTable_RenderHTML-8 200000 6626 ns/op 3921 B/op 44 allocs/op
BenchmarkTable_RenderMarkdown-8 300000 4602 ns/op 2400 B/op 43 allocs/op
```
2 changes: 1 addition & 1 deletion cmd/demo-progress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ Calculating Total # 13 ... done! [549.25K in 1.301s]
All done!
```

Real-time playback of the output @ asciinema.org:
Real-time playback of the demo @ asciinema.org:
[![asciicast](https://asciinema.org/a/KcPw8aoBSsYCBOj60wluhu5z3.png)](https://asciinema.org/a/KcPw8aoBSsYCBOj60wluhu5z3)
2 changes: 1 addition & 1 deletion progress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Downloading File # 9 ... 32.1% (●●●●●●●○◌◌◌◌◌◌
Transferring Amount # 10 ... 13.0% (●●○◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌) [£32.50K in 198.84ms]
```

Real-time playback of the output @ asciinema.org:
Real-time playback of the demo @ asciinema.org:
[![asciicast](https://asciinema.org/a/KcPw8aoBSsYCBOj60wluhu5z3.png)](https://asciinema.org/a/KcPw8aoBSsYCBOj60wluhu5z3)

A demonstration of all the capabilities can be found here:
Expand Down
5 changes: 0 additions & 5 deletions progress/progress.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package progress

import (
"fmt"
"io"
"math"
"os"
Expand Down Expand Up @@ -197,7 +196,3 @@ func (p *Progress) initForRender() {
p.updateFrequency = DefaultUpdateFrequency
}
}

func (p *Progress) write(a ...interface{}) {
p.outputWriter.Write([]byte(fmt.Sprint(a...)))
}
148 changes: 83 additions & 65 deletions progress/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ func (p *Progress) Render() {
p.initForRender()

c := time.Tick(p.updateFrequency)
lastRenderLength := 0
for p.renderInProgress = true; p.renderInProgress; {
select {
case <-c:
if len(p.trackersInQueue) > 0 || len(p.trackersActive) > 0 {
p.renderTrackers()
lastRenderLength = p.renderTrackers(lastRenderLength)
}
case <-p.done:
p.renderInProgress = false
Expand All @@ -28,24 +29,28 @@ func (p *Progress) Render() {
}
}

func (p *Progress) renderTrackers() {
func (p *Progress) renderTrackers(lastRenderLength int) int {
// buffer all output into a strings.Builder object
var out strings.Builder
out.Grow(lastRenderLength)

// move up N times based on the number of active trackers
if len(p.trackersActive) > 0 {
p.write(util.CursorUp.Sprintn(len(p.trackersActive)))
out.WriteString(util.CursorUp.Sprintn(len(p.trackersActive)))
}

// move trackers waiting in queue to the active list
if len(p.trackersInQueue) > 0 {
p.trackersInQueueMutex.Lock()
p.trackersActive = append(p.trackersActive, p.trackersInQueue...)
p.trackersInQueue = []*Tracker{}
p.trackersInQueue = make([]*Tracker, 0)
p.trackersInQueueMutex.Unlock()
}

// render the finished trackers and move them to the "done" list
for idx, tracker := range p.trackersActive {
if tracker.IsDone() {
p.renderTracker(tracker)
p.renderTracker(&out, tracker)
if idx < len(p.trackersActive) {
p.trackersActive = append(p.trackersActive[:idx], p.trackersActive[idx+1:]...)
}
Expand All @@ -56,109 +61,122 @@ func (p *Progress) renderTrackers() {
// sort and render the active trackers
p.sortBy.Sort(p.trackersActive)
for _, tracker := range p.trackersActive {
p.renderTracker(tracker)
p.renderTracker(&out, tracker)
}

// write the text to the output writer
p.outputWriter.Write([]byte(out.String()))

// stop if auto stop is enabled and there are no more active trackers
if p.autoStop && len(p.trackersInQueue) == 0 && len(p.trackersActive) == 0 {
p.done <- true
}
}

func (p *Progress) renderTracker(t *Tracker) {
p.write(util.EraseLine.Sprint())

pDotValue := float64(t.Total) / float64(p.lengthProgress)
pFinishedDots := float64(t.value) / pDotValue
pFinishedLen := int(math.Ceil(pFinishedDots))
pUnfinishedLen := p.lengthProgress - pFinishedLen
return out.Len()
}

var pFinished, pInProgress, pUnfinished string
if pFinishedLen > 0 {
pFinished = strings.Repeat(p.style.Chars.Finished, pFinishedLen-1)
}
if pUnfinishedLen > 0 {
pUnfinished = strings.Repeat(p.style.Chars.Unfinished, pUnfinishedLen)
}

pFinishedDecimals := pFinishedDots - float64(int(pFinishedDots))
if pFinishedDecimals > 0.75 {
pInProgress = p.style.Chars.Finished75
} else if pFinishedDecimals > 0.50 {
pInProgress = p.style.Chars.Finished50
} else if pFinishedDecimals > 0.25 {
pInProgress = p.style.Chars.Finished25
} else {
pInProgress = p.style.Chars.Unfinished
}
func (p *Progress) renderTracker(out *strings.Builder, t *Tracker) {
out.WriteString(util.EraseLine.Sprint())

if t.IsDone() {
p.renderTrackerDone(t)
p.renderTrackerDone(out, t)
} else {
p.renderTrackerProgress(t, p.style.Colors.Tracker.Sprintf("%s%s%s%s%s",
pDotValue := float64(t.Total) / float64(p.lengthProgress)
pFinishedDots := float64(t.value) / pDotValue
pFinishedLen := int(math.Ceil(pFinishedDots))
pUnfinishedLen := p.lengthProgress - pFinishedLen

var pFinished, pInProgress, pUnfinished string
if pFinishedLen > 0 {
pFinished = strings.Repeat(p.style.Chars.Finished, pFinishedLen-1)
}
if pUnfinishedLen > 0 {
pUnfinished = strings.Repeat(p.style.Chars.Unfinished, pUnfinishedLen)
}

pFinishedDecimals := pFinishedDots - float64(int(pFinishedDots))
if pFinishedDecimals > 0.75 {
pInProgress = p.style.Chars.Finished75
} else if pFinishedDecimals > 0.50 {
pInProgress = p.style.Chars.Finished50
} else if pFinishedDecimals > 0.25 {
pInProgress = p.style.Chars.Finished25
} else {
pInProgress = p.style.Chars.Unfinished
}

p.renderTrackerProgress(out, t, p.style.Colors.Tracker.Sprintf("%s%s%s%s%s",
p.style.Chars.BoxLeft, pFinished, pInProgress, pUnfinished, p.style.Chars.BoxRight,
))
}
}

func (p *Progress) renderTrackerDone(t *Tracker) {
p.write(p.style.Colors.Message.Sprint(t.Message))
p.write(" " + p.style.Options.MessageTrackerSeparator + " ")
p.write(p.style.Colors.Done.Sprint(p.style.Options.DoneString))
p.renderTrackerValueAndTime(t)
p.write("\n")
func (p *Progress) renderTrackerDone(out *strings.Builder, t *Tracker) {
out.WriteString(p.style.Colors.Message.Sprint(t.Message))
out.WriteRune(' ')
out.WriteString(p.style.Options.MessageTrackerSeparator)
out.WriteRune(' ')
out.WriteString(p.style.Colors.Done.Sprint(p.style.Options.DoneString))
p.renderTrackerStats(out, t)
out.WriteRune('\n')
}

func (p *Progress) renderTrackerProgress(t *Tracker, trackerStr string) {
func (p *Progress) renderTrackerProgress(out *strings.Builder, t *Tracker, trackerStr string) {
if p.trackerPosition == PositionRight {
p.write(p.style.Colors.Message.Sprint(t.Message))
p.write(" " + p.style.Options.MessageTrackerSeparator + " ")
p.renderTrackerPercentage(t)
out.WriteString(p.style.Colors.Message.Sprint(t.Message))
out.WriteRune(' ')
out.WriteString(p.style.Options.MessageTrackerSeparator)
out.WriteRune(' ')
p.renderTrackerPercentage(out, t)
if !p.hideTracker {
p.write(" " + p.style.Colors.Tracker.Sprint(trackerStr))
out.WriteRune(' ')
out.WriteString(p.style.Colors.Tracker.Sprint(trackerStr))
}
p.renderTrackerValueAndTime(t)
p.write("\n")
p.renderTrackerStats(out, t)
out.WriteString("\n")
} else {
p.renderTrackerPercentage(t)
p.renderTrackerPercentage(out, t)
if !p.hideTracker {
p.write(" " + p.style.Colors.Tracker.Sprint(trackerStr))
out.WriteRune(' ')
out.WriteString(p.style.Colors.Tracker.Sprint(trackerStr))
}
p.renderTrackerValueAndTime(t)
p.write(" " + p.style.Options.MessageTrackerSeparator + " ")
p.write(p.style.Colors.Message.Sprint(t.Message))
p.write("\n")
p.renderTrackerStats(out, t)
out.WriteRune(' ')
out.WriteString(p.style.Options.MessageTrackerSeparator)
out.WriteRune(' ')
out.WriteString(p.style.Colors.Message.Sprint(t.Message))
out.WriteRune(' ')
}
}

func (p *Progress) renderTrackerPercentage(t *Tracker) {
func (p *Progress) renderTrackerPercentage(out *strings.Builder, t *Tracker) {
if !p.hidePercentage {
p.write(p.style.Colors.Percent.Sprintf(p.style.Options.PercentFormat, t.PercentDone()))
out.WriteString(p.style.Colors.Percent.Sprintf(p.style.Options.PercentFormat, t.PercentDone()))
}
}

func (p *Progress) renderTrackerValueAndTime(t *Tracker) {
func (p *Progress) renderTrackerStats(out *strings.Builder, t *Tracker) {
if !p.hideValue || !p.hideTime {
var out strings.Builder
out.WriteString(" [")
var outStats strings.Builder
outStats.WriteString(" [")
if !p.hideValue {
out.WriteString(p.style.Colors.Value.Sprint(t.Units.Sprint(t.value)))
outStats.WriteString(p.style.Colors.Value.Sprint(t.Units.Sprint(t.value)))
}
if !p.hideValue && !p.hideTime {
out.WriteString(" ")
outStats.WriteRune(' ')
}
if !p.hideTime {
out.WriteString("in ")
outStats.WriteString("in ")
if t.IsDone() {
out.WriteString(p.style.Colors.Time.Sprint(
outStats.WriteString(p.style.Colors.Time.Sprint(
t.timeStop.Sub(t.timeStart).Round(p.style.Options.TimeDonePrecision)))
} else {
out.WriteString(p.style.Colors.Time.Sprint(
outStats.WriteString(p.style.Colors.Time.Sprint(
time.Since(t.timeStart).Round(p.style.Options.TimeInProgressPrecision)))
}
}
out.WriteString("]")
outStats.WriteRune(']')

p.write(p.style.Colors.Stats.Sprint(out.String()))
out.WriteString(p.style.Colors.Stats.Sprint(outStats.String()))
}
}
6 changes: 2 additions & 4 deletions progress/tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,8 @@ func (tu Units) sprintAll(value int64) string {
return fmt.Sprintf("%.2fB", float64(value)/1000000000.0)
} else if value < 1000000000000000 {
return fmt.Sprintf("%.2fT", float64(value)/1000000000000.0)
} else {
return fmt.Sprintf("%.2fQ", float64(value)/1000000000000000.0)
}
return fmt.Sprintf("%.2fQ", float64(value)/1000000000000000.0)
}

func (tu Units) sprintBytes(value int64) string {
Expand All @@ -145,9 +144,8 @@ func (tu Units) sprintBytes(value int64) string {
return fmt.Sprintf("%.2fGB", float64(value)/1000000000.0)
} else if value < 1000000000000000 {
return fmt.Sprintf("%.2fTB", float64(value)/1000000000000.0)
} else {
return fmt.Sprintf("%.2fPB", float64(value)/1000000000000000.0)
}
return fmt.Sprintf("%.2fPB", float64(value)/1000000000000000.0)
}

// SortBy helps sort a list of Trackers by various means.
Expand Down

0 comments on commit 844e6f1

Please sign in to comment.