Skip to content

Commit

Permalink
Merge fc0bb5a into 918cee6
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t committed May 30, 2019
2 parents 918cee6 + fc0bb5a commit 48a0a07
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ before_script:

# CI Pipeline.
script:
- make test bench
- make test test-race bench
- $GOPATH/bin/goveralls -service=travis-pro -coverprofile=.coverprofile
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@ profile:
test: fmt lint vet cyclo
go test -cover -coverprofile=.coverprofile $(shell go list ./...)

test-race:
go run -race ./cmd/demo-progress/demo.go

vet:
go vet $(shell go list ./...)
2 changes: 1 addition & 1 deletion cmd/demo-progress/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func trackSomething(pw progress.Writer, idx int64) {

pw.AppendTracker(&tracker)

c := time.Tick(time.Millisecond * 250)
c := time.Tick(time.Millisecond * 100)
for !tracker.IsDone() {
select {
case <-c:
Expand Down
86 changes: 62 additions & 24 deletions progress/progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,32 @@ var (

// Progress helps track progress for one or more tasks.
type Progress struct {
autoStop bool
done chan bool
lengthTracker int
lengthProgress int
outputWriter io.Writer
hideTime bool
hideTracker bool
hideValue bool
hidePercentage bool
messageWidth int
numTrackersExpected int64
overallTracker *Tracker
renderInProgress bool
showOverallTracker bool
sortBy SortBy
style *Style
trackerPosition Position
trackersActive []*Tracker
trackersDone []*Tracker
trackersInQueue []*Tracker
trackersInQueueMutex sync.Mutex
updateFrequency time.Duration
autoStop bool
done chan bool
lengthTracker int
lengthProgress int
outputWriter io.Writer
hideTime bool
hideTracker bool
hideValue bool
hidePercentage bool
messageWidth int
numTrackersExpected int64
overallTracker *Tracker
overallTrackerMutex sync.RWMutex
renderInProgress bool
renderInProgressMutex sync.RWMutex
showOverallTracker bool
sortBy SortBy
style *Style
trackerPosition Position
trackersActive []*Tracker
trackersActiveMutex sync.RWMutex
trackersDone []*Tracker
trackersDoneMutex sync.RWMutex
trackersInQueue []*Tracker
trackersInQueueMutex sync.RWMutex
updateFrequency time.Duration
}

// Position defines the position of the Tracker with respect to the Tracker's
Expand All @@ -64,6 +68,7 @@ func (p *Progress) AppendTracker(t *Tracker) {
t.Total = math.MaxInt64
}
t.start()
p.overallTrackerMutex.Lock()
if p.overallTracker == nil {
p.overallTracker = &Tracker{Total: 1}
if p.numTrackersExpected > 0 {
Expand All @@ -73,10 +78,11 @@ func (p *Progress) AppendTracker(t *Tracker) {
}
p.trackersInQueueMutex.Lock()
p.trackersInQueue = append(p.trackersInQueue, t)
p.trackersInQueueMutex.Unlock()
if p.overallTracker.Total < int64(p.Length())*100 {
p.overallTracker.Total = int64(p.Length()) * 100
}
p.trackersInQueueMutex.Unlock()
p.overallTrackerMutex.Unlock()
}

// AppendTrackers appends one or more Trackers for tracking.
Expand All @@ -89,19 +95,51 @@ func (p *Progress) AppendTrackers(trackers []*Tracker) {
// IsRenderInProgress returns true if a call to Render() was made, and is still
// in progress and has not ended yet.
func (p *Progress) IsRenderInProgress() bool {
p.renderInProgressMutex.RLock()
defer p.renderInProgressMutex.RUnlock()

return p.renderInProgress
}

// Length returns the number of Trackers tracked overall.
func (p *Progress) Length() int {
p.trackersActiveMutex.RLock()
p.trackersDoneMutex.RLock()
p.trackersInQueueMutex.RLock()
defer p.trackersActiveMutex.RUnlock()
defer p.trackersDoneMutex.RUnlock()
defer p.trackersInQueueMutex.RUnlock()

return len(p.trackersInQueue) + len(p.trackersActive) + len(p.trackersDone)
}

// LengthActive returns the number of Trackers actively tracked (not done yet).
func (p *Progress) LengthActive() int {
p.trackersActiveMutex.RLock()
p.trackersInQueueMutex.RLock()
defer p.trackersActiveMutex.RUnlock()
defer p.trackersInQueueMutex.RUnlock()

return len(p.trackersInQueue) + len(p.trackersActive)
}

// LengthDone returns the number of Trackers that are done tracking.
func (p *Progress) LengthDone() int {
p.trackersDoneMutex.RLock()
defer p.trackersDoneMutex.RUnlock()

return len(p.trackersDone)
}

// LengthInQueue returns the number of Trackers in queue to be actively tracked
// (not tracking yet).
func (p *Progress) LengthInQueue() int {
p.trackersInQueueMutex.RLock()
defer p.trackersInQueueMutex.RUnlock()

return len(p.trackersInQueue)
}

// SetAutoStop toggles the auto-stop functionality. Auto-stop set to true would
// mean that the Render() function will automatically stop once all currently
// active Trackers reach their final states. When set to false, the client code
Expand Down Expand Up @@ -186,7 +224,7 @@ func (p *Progress) ShowValue(show bool) {

// Stop stops the Render() logic that is in progress.
func (p *Progress) Stop() {
if p.renderInProgress {
if p.IsRenderInProgress() {
p.done <- true
}
}
Expand Down
31 changes: 29 additions & 2 deletions progress/progress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,45 @@ func TestProgress_Length(t *testing.T) {
p := Progress{}
assert.Equal(t, 0, p.Length())

p.AppendTracker(&Tracker{})
p.trackersActive = append(p.trackersActive, &Tracker{})
assert.Equal(t, 1, p.Length())
p.trackersInQueue = append(p.trackersInQueue, &Tracker{})
assert.Equal(t, 2, p.Length())
p.trackersDone = append(p.trackersDone, &Tracker{})
assert.Equal(t, 3, p.Length())
}

func TestProgress_LengthActive(t *testing.T) {
p := Progress{}
assert.Equal(t, 0, p.Length())
assert.Equal(t, 0, p.LengthActive())

p.AppendTracker(&Tracker{})
p.trackersActive = append(p.trackersActive, &Tracker{})
assert.Equal(t, 1, p.Length())
assert.Equal(t, 1, p.LengthActive())
p.trackersInQueue = append(p.trackersInQueue, &Tracker{})
assert.Equal(t, 2, p.Length())
assert.Equal(t, 2, p.LengthActive())
}

func TestProgress_LengthDone(t *testing.T) {
p := Progress{}
assert.Equal(t, 0, p.Length())
assert.Equal(t, 0, p.LengthDone())

p.trackersDone = append(p.trackersDone, &Tracker{})
assert.Equal(t, 1, p.Length())
assert.Equal(t, 1, p.LengthDone())
}

func TestProgress_LengthInQueue(t *testing.T) {
p := Progress{}
assert.Equal(t, 0, p.Length())
assert.Equal(t, 0, p.LengthInQueue())

p.trackersInQueue = append(p.trackersInQueue, &Tracker{})
assert.Equal(t, 1, p.Length())
assert.Equal(t, 1, p.LengthInQueue())
}

func TestProgress_SetAutoStop(t *testing.T) {
Expand Down
27 changes: 21 additions & 6 deletions progress/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,24 @@ import (
// Render renders the Progress tracker and handles all existing trackers and
// those that are added dynamically while render is in progress.
func (p *Progress) Render() {
if !p.renderInProgress {
if !p.IsRenderInProgress() {
p.initForRender()

c := time.Tick(p.updateFrequency)
lastRenderLength := 0
for p.renderInProgress = true; p.renderInProgress; {
p.renderInProgressMutex.Lock()
p.renderInProgress = true
p.renderInProgressMutex.Unlock()
for p.IsRenderInProgress() {
select {
case <-c:
if len(p.trackersInQueue) > 0 || len(p.trackersActive) > 0 {
if p.LengthActive() > 0 {
lastRenderLength = p.renderTrackers(lastRenderLength)
}
case <-p.done:
p.renderInProgressMutex.Lock()
p.renderInProgress = false
p.renderInProgressMutex.Unlock()
}
}
}
Expand All @@ -46,13 +51,17 @@ func (p *Progress) renderTrackers(lastRenderLength int) int {
for _, tracker := range trackersDone {
p.renderTracker(&out, tracker, renderHint{})
}
p.trackersDoneMutex.Lock()
p.trackersDone = append(p.trackersDone, trackersDone...)
p.trackersDoneMutex.Unlock()

// sort and render the active trackers
for _, tracker := range trackersActive {
p.renderTracker(&out, tracker, renderHint{})
}
p.trackersActiveMutex.Lock()
p.trackersActive = trackersActive
p.trackersActiveMutex.Unlock()

// render the overall tracker
p.renderTracker(&out, p.overallTracker, renderHint{isOverallTracker: true})
Expand All @@ -61,18 +70,20 @@ func (p *Progress) renderTrackers(lastRenderLength int) int {
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 {
if p.autoStop && p.LengthActive() == 0 {
p.done <- true
}

return out.Len()
}

func (p *Progress) consumeQueuedTrackers() {
if len(p.trackersInQueue) > 0 {
if p.LengthInQueue() > 0 {
p.trackersActiveMutex.Lock()
p.trackersInQueueMutex.Lock()
p.trackersActive = append(p.trackersActive, p.trackersInQueue...)
p.trackersInQueue = make([]*Tracker, 0)
p.trackersActiveMutex.Unlock()
p.trackersInQueueMutex.Unlock()
}
}
Expand All @@ -84,6 +95,7 @@ func (p *Progress) extractDoneAndActiveTrackers() ([]*Tracker, []*Tracker) {
// separate the active and done trackers
var trackersActive, trackersDone []*Tracker
var activeTrackersProgress int64
p.trackersActiveMutex.RLock()
for _, tracker := range p.trackersActive {
if !tracker.IsDone() {
trackersActive = append(trackersActive, tracker)
Expand All @@ -92,11 +104,12 @@ func (p *Progress) extractDoneAndActiveTrackers() ([]*Tracker, []*Tracker) {
trackersDone = append(trackersDone, tracker)
}
}
p.trackersActiveMutex.RUnlock()
p.sortBy.Sort(trackersDone)
p.sortBy.Sort(trackersActive)

// calculate the overall tracker's progress value
p.overallTracker.value = int64(len(p.trackersDone)+len(trackersDone)) * 100
p.overallTracker.value = int64(p.LengthDone()+len(trackersDone)) * 100
p.overallTracker.value += activeTrackersProgress
if len(trackersActive) == 0 {
p.overallTracker.MarkAsDone()
Expand All @@ -105,10 +118,12 @@ func (p *Progress) extractDoneAndActiveTrackers() ([]*Tracker, []*Tracker) {
}

func (p *Progress) generateTrackerStr(t *Tracker, maxLen int) string {
t.mutex.Lock()
pDotValue := float64(t.Total) / float64(maxLen)
pFinishedDots := float64(t.value) / pDotValue
pFinishedDotsFraction := pFinishedDots - float64(int(pFinishedDots))
pFinishedLen := int(math.Floor(pFinishedDots))
t.mutex.Unlock()

var pFinished, pInProgress, pUnfinished string
if pFinishedLen > 0 {
Expand Down

0 comments on commit 48a0a07

Please sign in to comment.