stats: fix the speed not getting updated after a pause in the processing

This shifts the behavior of the average loop to be a persistent loop
that gets resumed/paused when transfers & checks are started/completed.

Previously, the averageLoop was stopped on completion of
transfers & checks but failed to start again due to the protection of
the sync.Once

Signed-off-by: Anagh Kumar Baranwal <6824881+darthShadow@users.noreply.github.com>
This commit is contained in:
Anagh Kumar Baranwal 2024-09-17 18:09:54 +05:30
parent f639cd9c78
commit 40bba359e2
No known key found for this signature in database

View File

@ -71,22 +71,23 @@ type averageValues struct {
speed float64 speed float64
stop chan bool stop chan bool
stopped sync.WaitGroup stopped sync.WaitGroup
startOnce sync.Once started bool
stopOnce sync.Once
} }
// NewStats creates an initialised StatsInfo // NewStats creates an initialised StatsInfo
func NewStats(ctx context.Context) *StatsInfo { func NewStats(ctx context.Context) *StatsInfo {
ci := fs.GetConfig(ctx) ci := fs.GetConfig(ctx)
return &StatsInfo{ s := &StatsInfo{
ctx: ctx, ctx: ctx,
ci: ci, ci: ci,
checking: newTransferMap(ci.Checkers, "checking"), checking: newTransferMap(ci.Checkers, "checking"),
transferring: newTransferMap(ci.Transfers, "transferring"), transferring: newTransferMap(ci.Transfers, "transferring"),
inProgress: newInProgress(ctx), inProgress: newInProgress(ctx),
startTime: time.Now(), startTime: time.Now(),
average: averageValues{stop: make(chan bool)}, average: averageValues{},
} }
s.startAverageLoop()
return s
} }
// RemoteStats returns stats for rc // RemoteStats returns stats for rc
@ -328,61 +329,97 @@ func (s *StatsInfo) averageLoop() {
ticker := time.NewTicker(averagePeriodLength) ticker := time.NewTicker(averagePeriodLength)
defer ticker.Stop() defer ticker.Stop()
startTime := time.Now()
a := &s.average a := &s.average
defer a.stopped.Done() defer a.stopped.Done()
shouldRun := false
for { for {
select { select {
case now := <-ticker.C: case now := <-ticker.C:
a.mu.Lock() a.mu.Lock()
var elapsed float64
if a.lpTime.IsZero() { if !shouldRun {
elapsed = now.Sub(startTime).Seconds() a.mu.Unlock()
} else { continue
elapsed = now.Sub(a.lpTime).Seconds()
} }
avg := 0.0 avg := 0.0
elapsed := now.Sub(a.lpTime).Seconds()
if elapsed > 0 { if elapsed > 0 {
avg = float64(a.lpBytes) / elapsed avg = float64(a.lpBytes) / elapsed
} }
if period < averagePeriod { if period < averagePeriod {
period++ period++
} }
a.speed = (avg + a.speed*(period-1)) / period a.speed = (avg + a.speed*(period-1)) / period
a.lpBytes = 0 a.lpBytes = 0
a.lpTime = now a.lpTime = now
a.mu.Unlock() a.mu.Unlock()
case <-a.stop:
return case stop, ok := <-a.stop:
if !ok {
return // Channel closed, exit the loop
} }
a.mu.Lock()
// If we are resuming, store the current time
if !shouldRun && !stop {
a.lpTime = time.Now()
}
shouldRun = !stop
a.mu.Unlock()
}
}
}
// Resume the average loop
func (s *StatsInfo) resumeAverageLoop() {
s.mu.Lock()
defer s.mu.Unlock()
s.average.stop <- false
}
// Pause the average loop
func (s *StatsInfo) pauseAverageLoop() {
s.mu.Lock()
defer s.mu.Unlock()
s.average.stop <- true
}
// Start the average loop
//
// Call with the mutex held
func (s *StatsInfo) _startAverageLoop() {
if !s.average.started {
s.average.stop = make(chan bool)
s.average.started = true
s.average.stopped.Add(1)
go s.averageLoop()
} }
} }
// Start the average loop // Start the average loop
func (s *StatsInfo) startAverageLoop() { func (s *StatsInfo) startAverageLoop() {
s.mu.RLock() s.mu.Lock()
defer s.mu.RUnlock() defer s.mu.Unlock()
s.average.startOnce.Do(func() { s._startAverageLoop()
s.average.stopped.Add(1)
go s.averageLoop()
})
} }
// Stop the average loop // Stop the average loop
// //
// Call with the mutex held // Call with the mutex held
func (s *StatsInfo) _stopAverageLoop() { func (s *StatsInfo) _stopAverageLoop() {
s.average.stopOnce.Do(func() { if s.average.started {
close(s.average.stop) close(s.average.stop)
s.average.stopped.Wait() s.average.stopped.Wait()
}) s.average.started = false
} }
// Stop the average loop
func (s *StatsInfo) stopAverageLoop() {
s.mu.RLock()
defer s.mu.RUnlock()
s._stopAverageLoop()
} }
// String convert the StatsInfo to a string for printing // String convert the StatsInfo to a string for printing
@ -564,9 +601,9 @@ func (s *StatsInfo) GetBytesWithPending() int64 {
pending := int64(0) pending := int64(0)
for _, tr := range s.startedTransfers { for _, tr := range s.startedTransfers {
if tr.acc != nil { if tr.acc != nil {
bytes, size := tr.acc.progress() bytesRead, size := tr.acc.progress()
if bytes < size { if bytesRead < size {
pending += size - bytes pending += size - bytesRead
} }
} }
} }
@ -699,7 +736,8 @@ func (s *StatsInfo) ResetCounters() {
s.oldDuration = 0 s.oldDuration = 0
s._stopAverageLoop() s._stopAverageLoop()
s.average = averageValues{stop: make(chan bool)} s.average = averageValues{}
s._startAverageLoop()
} }
// ResetErrors sets the errors count to 0 and resets lastError, fatalError and retryError // ResetErrors sets the errors count to 0 and resets lastError, fatalError and retryError
@ -788,7 +826,7 @@ func (s *StatsInfo) NewTransfer(obj fs.DirEntry, dstFs fs.Fs) *Transfer {
} }
tr := newTransfer(s, obj, srcFs, dstFs) tr := newTransfer(s, obj, srcFs, dstFs)
s.transferring.add(tr) s.transferring.add(tr)
s.startAverageLoop() s.resumeAverageLoop()
return tr return tr
} }
@ -796,7 +834,7 @@ func (s *StatsInfo) NewTransfer(obj fs.DirEntry, dstFs fs.Fs) *Transfer {
func (s *StatsInfo) NewTransferRemoteSize(remote string, size int64, srcFs, dstFs fs.Fs) *Transfer { func (s *StatsInfo) NewTransferRemoteSize(remote string, size int64, srcFs, dstFs fs.Fs) *Transfer {
tr := newTransferRemoteSize(s, remote, size, false, "", srcFs, dstFs) tr := newTransferRemoteSize(s, remote, size, false, "", srcFs, dstFs)
s.transferring.add(tr) s.transferring.add(tr)
s.startAverageLoop() s.resumeAverageLoop()
return tr return tr
} }
@ -811,7 +849,7 @@ func (s *StatsInfo) DoneTransferring(remote string, ok bool) {
s.mu.Unlock() s.mu.Unlock()
} }
if s.transferring.empty() && s.checking.empty() { if s.transferring.empty() && s.checking.empty() {
time.AfterFunc(averageStopAfter, s.stopAverageLoop) s.pauseAverageLoop()
} }
} }