accounting: fix total duration calculation
This was broken in e337cae0c5
when we deleted the transfers
immediately.
This is fixed by keeping a merged slice of time ranges of completed
transfers and adding those to the current transfers.
This commit is contained in:
parent
38652d046d
commit
b002ff8d54
2 changed files with 167 additions and 42 deletions
|
@ -34,7 +34,8 @@ type StatsInfo struct {
|
||||||
renameQueueSize int64
|
renameQueueSize int64
|
||||||
deletes int64
|
deletes int64
|
||||||
inProgress *inProgress
|
inProgress *inProgress
|
||||||
startedTransfers []*Transfer
|
startedTransfers []*Transfer // currently active transfers
|
||||||
|
oldTimeRanges timeRanges // a merged list of time ranges for the transfers
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStats creates an initialised StatsInfo
|
// NewStats creates an initialised StatsInfo
|
||||||
|
@ -109,50 +110,76 @@ func (s *StatsInfo) transferRemoteStats(name string) rc.Params {
|
||||||
return rc.Params{"name": name}
|
return rc.Params{"name": name}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeRange is a start and end time of a transfer
|
||||||
type timeRange struct {
|
type timeRange struct {
|
||||||
start time.Time
|
start time.Time
|
||||||
end time.Time
|
end time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeRanges is a list of non-overlapping start and end times for
|
||||||
|
// transfers
|
||||||
|
type timeRanges []timeRange
|
||||||
|
|
||||||
|
// merge all the overlapping time ranges
|
||||||
|
func (trs *timeRanges) merge() {
|
||||||
|
Trs := *trs
|
||||||
|
|
||||||
|
// Sort by the starting time.
|
||||||
|
sort.Slice(Trs, func(i, j int) bool {
|
||||||
|
return Trs[i].start.Before(Trs[j].start)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Merge overlaps and add distinctive ranges together
|
||||||
|
var (
|
||||||
|
newTrs = Trs[:0]
|
||||||
|
i, j = 0, 1
|
||||||
|
)
|
||||||
|
for i < len(Trs) {
|
||||||
|
if j < len(Trs) {
|
||||||
|
if !Trs[i].end.Before(Trs[j].start) {
|
||||||
|
if Trs[i].end.Before(Trs[j].end) {
|
||||||
|
Trs[i].end = Trs[j].end
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newTrs = append(newTrs, Trs[i])
|
||||||
|
i = j
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
*trs = newTrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// total the time out of the time ranges
|
||||||
|
func (trs timeRanges) total() (total time.Duration) {
|
||||||
|
for _, tr := range trs {
|
||||||
|
total += tr.end.Sub(tr.start)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
// Total duration is union of durations of all transfers belonging to this
|
// Total duration is union of durations of all transfers belonging to this
|
||||||
// object.
|
// object.
|
||||||
// Needs to be protected by mutex.
|
// Needs to be protected by mutex.
|
||||||
func (s *StatsInfo) totalDuration() time.Duration {
|
func (s *StatsInfo) totalDuration() time.Duration {
|
||||||
now := time.Now()
|
// copy of s.oldTimeRanges with extra room for the current transfers
|
||||||
|
timeRanges := make(timeRanges, len(s.oldTimeRanges), len(s.oldTimeRanges)+len(s.startedTransfers))
|
||||||
|
copy(timeRanges, s.oldTimeRanges)
|
||||||
|
|
||||||
// Extract time ranges of all transfers.
|
// Extract time ranges of all transfers.
|
||||||
timeRanges := make([]timeRange, len(s.startedTransfers))
|
now := time.Now()
|
||||||
for i := range s.startedTransfers {
|
for i := range s.startedTransfers {
|
||||||
start, end := s.startedTransfers[i].TimeRange()
|
start, end := s.startedTransfers[i].TimeRange()
|
||||||
if end.IsZero() {
|
if end.IsZero() {
|
||||||
end = now
|
end = now
|
||||||
}
|
}
|
||||||
timeRanges[i] = timeRange{start, end}
|
timeRanges = append(timeRanges, timeRange{start, end})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by the starting time.
|
timeRanges.merge()
|
||||||
sort.Slice(timeRanges, func(i, j int) bool {
|
return timeRanges.total()
|
||||||
return timeRanges[i].start.Before(timeRanges[j].start)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Merge overlaps and add distinctive ranges together for total.
|
|
||||||
var total time.Duration
|
|
||||||
var i, j = 0, 1
|
|
||||||
for i < len(timeRanges) {
|
|
||||||
if j < len(timeRanges) {
|
|
||||||
if timeRanges[j].start.Before(timeRanges[i].end) {
|
|
||||||
if timeRanges[i].end.Before(timeRanges[j].end) {
|
|
||||||
timeRanges[i].end = timeRanges[j].end
|
|
||||||
}
|
|
||||||
j++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total += timeRanges[i].end.Sub(timeRanges[i].start)
|
|
||||||
i = j
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
|
|
||||||
return total
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eta returns the ETA of the current operation,
|
// eta returns the ETA of the current operation,
|
||||||
|
@ -536,6 +563,15 @@ func (s *StatsInfo) AddTransfer(transfer *Transfer) {
|
||||||
// RemoveTransfer removes a reference to the started transfer.
|
// RemoveTransfer removes a reference to the started transfer.
|
||||||
func (s *StatsInfo) RemoveTransfer(transfer *Transfer) {
|
func (s *StatsInfo) RemoveTransfer(transfer *Transfer) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
|
||||||
|
// add finished transfer onto old time ranges
|
||||||
|
start, end := transfer.TimeRange()
|
||||||
|
if end.IsZero() {
|
||||||
|
end = time.Now()
|
||||||
|
}
|
||||||
|
s.oldTimeRanges = append(s.oldTimeRanges, timeRange{start, end})
|
||||||
|
s.oldTimeRanges.merge()
|
||||||
|
|
||||||
for i, tr := range s.startedTransfers {
|
for i, tr := range s.startedTransfers {
|
||||||
if tr == transfer {
|
if tr == transfer {
|
||||||
// remove the found entry
|
// remove the found entry
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rclone/rclone/fs/fserrors"
|
"github.com/rclone/rclone/fs/fserrors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestETA(t *testing.T) {
|
func TestETA(t *testing.T) {
|
||||||
|
@ -138,52 +139,65 @@ func TestStatsTotalDuration(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Single completed transfer", func(t *testing.T) {
|
t.Run("Single completed transfer", func(t *testing.T) {
|
||||||
s := NewStats()
|
s := NewStats()
|
||||||
s.AddTransfer(&Transfer{
|
tr1 := &Transfer{
|
||||||
startedAt: time1,
|
startedAt: time1,
|
||||||
completedAt: time2,
|
completedAt: time2,
|
||||||
})
|
}
|
||||||
|
s.AddTransfer(tr1)
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
total := s.totalDuration()
|
total := s.totalDuration()
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(s.startedTransfers))
|
||||||
assert.Equal(t, 10*time.Second, total)
|
assert.Equal(t, 10*time.Second, total)
|
||||||
|
s.RemoveTransfer(tr1)
|
||||||
|
assert.Equal(t, 10*time.Second, total)
|
||||||
|
assert.Equal(t, 0, len(s.startedTransfers))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Single uncompleted transfer", func(t *testing.T) {
|
t.Run("Single uncompleted transfer", func(t *testing.T) {
|
||||||
s := NewStats()
|
s := NewStats()
|
||||||
s.AddTransfer(&Transfer{
|
tr1 := &Transfer{
|
||||||
startedAt: time1,
|
startedAt: time1,
|
||||||
})
|
}
|
||||||
|
s.AddTransfer(tr1)
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
total := s.totalDuration()
|
total := s.totalDuration()
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
assert.Equal(t, time.Since(time1)/time.Second, total/time.Second)
|
assert.Equal(t, time.Since(time1)/time.Second, total/time.Second)
|
||||||
|
s.RemoveTransfer(tr1)
|
||||||
|
assert.Equal(t, time.Since(time1)/time.Second, total/time.Second)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Overlapping without ending", func(t *testing.T) {
|
t.Run("Overlapping without ending", func(t *testing.T) {
|
||||||
s := NewStats()
|
s := NewStats()
|
||||||
s.AddTransfer(&Transfer{
|
tr1 := &Transfer{
|
||||||
startedAt: time2,
|
startedAt: time2,
|
||||||
completedAt: time3,
|
completedAt: time3,
|
||||||
})
|
}
|
||||||
s.AddTransfer(&Transfer{
|
s.AddTransfer(tr1)
|
||||||
|
tr2 := &Transfer{
|
||||||
startedAt: time2,
|
startedAt: time2,
|
||||||
completedAt: time2.Add(time.Second),
|
completedAt: time2.Add(time.Second),
|
||||||
})
|
}
|
||||||
s.AddTransfer(&Transfer{
|
s.AddTransfer(tr2)
|
||||||
|
tr3 := &Transfer{
|
||||||
startedAt: time1,
|
startedAt: time1,
|
||||||
completedAt: time3,
|
completedAt: time3,
|
||||||
})
|
}
|
||||||
s.AddTransfer(&Transfer{
|
s.AddTransfer(tr3)
|
||||||
|
tr4 := &Transfer{
|
||||||
startedAt: time3,
|
startedAt: time3,
|
||||||
completedAt: time4,
|
completedAt: time4,
|
||||||
})
|
}
|
||||||
s.AddTransfer(&Transfer{
|
s.AddTransfer(tr4)
|
||||||
|
tr5 := &Transfer{
|
||||||
startedAt: time.Now(),
|
startedAt: time.Now(),
|
||||||
})
|
}
|
||||||
|
s.AddTransfer(tr5)
|
||||||
|
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
|
|
||||||
|
@ -192,6 +206,14 @@ func TestStatsTotalDuration(t *testing.T) {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
assert.Equal(t, time.Duration(30), total/time.Second)
|
assert.Equal(t, time.Duration(30), total/time.Second)
|
||||||
|
s.RemoveTransfer(tr1)
|
||||||
|
assert.Equal(t, time.Duration(30), total/time.Second)
|
||||||
|
s.RemoveTransfer(tr2)
|
||||||
|
assert.Equal(t, time.Duration(30), total/time.Second)
|
||||||
|
s.RemoveTransfer(tr3)
|
||||||
|
assert.Equal(t, time.Duration(30), total/time.Second)
|
||||||
|
s.RemoveTransfer(tr4)
|
||||||
|
assert.Equal(t, time.Duration(30), total/time.Second)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Mixed completed and uncompleted transfers", func(t *testing.T) {
|
t.Run("Mixed completed and uncompleted transfers", func(t *testing.T) {
|
||||||
|
@ -217,3 +239,70 @@ func TestStatsTotalDuration(t *testing.T) {
|
||||||
assert.Equal(t, startTime.Sub(time1)/time.Second, total/time.Second)
|
assert.Equal(t, startTime.Sub(time1)/time.Second, total/time.Second)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make time ranges from string description for testing
|
||||||
|
func makeTimeRanges(t *testing.T, in []string) timeRanges {
|
||||||
|
trs := make(timeRanges, len(in))
|
||||||
|
for i, Range := range in {
|
||||||
|
var start, end int64
|
||||||
|
n, err := fmt.Sscanf(Range, "%d-%d", &start, &end)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, n)
|
||||||
|
trs[i] = timeRange{time.Unix(start, 0), time.Unix(end, 0)}
|
||||||
|
}
|
||||||
|
return trs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeRangeMerge(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in []string
|
||||||
|
want []string
|
||||||
|
}{{
|
||||||
|
in: []string{},
|
||||||
|
want: []string{},
|
||||||
|
}, {
|
||||||
|
in: []string{"1-2"},
|
||||||
|
want: []string{"1-2"},
|
||||||
|
}, {
|
||||||
|
in: []string{"1-4", "2-3"},
|
||||||
|
want: []string{"1-4"},
|
||||||
|
}, {
|
||||||
|
in: []string{"2-3", "1-4"},
|
||||||
|
want: []string{"1-4"},
|
||||||
|
}, {
|
||||||
|
in: []string{"1-3", "2-4"},
|
||||||
|
want: []string{"1-4"},
|
||||||
|
}, {
|
||||||
|
in: []string{"2-4", "1-3"},
|
||||||
|
want: []string{"1-4"},
|
||||||
|
}, {
|
||||||
|
in: []string{"1-2", "2-3"},
|
||||||
|
want: []string{"1-3"},
|
||||||
|
}, {
|
||||||
|
in: []string{"2-3", "1-2"},
|
||||||
|
want: []string{"1-3"},
|
||||||
|
}, {
|
||||||
|
in: []string{"1-2", "3-4"},
|
||||||
|
want: []string{"1-2", "3-4"},
|
||||||
|
}, {
|
||||||
|
in: []string{"1-3", "7-8", "4-6", "2-5", "7-8", "7-8"},
|
||||||
|
want: []string{"1-6", "7-8"},
|
||||||
|
}} {
|
||||||
|
|
||||||
|
in := makeTimeRanges(t, test.in)
|
||||||
|
in.merge()
|
||||||
|
|
||||||
|
got := []string{}
|
||||||
|
for _, tr := range in {
|
||||||
|
got = append(got, fmt.Sprintf("%d-%d", tr.start.Unix(), tr.end.Unix()))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeRangeDuration(t *testing.T) {
|
||||||
|
assert.Equal(t, 0*time.Second, timeRanges{}.total())
|
||||||
|
assert.Equal(t, 1*time.Second, makeTimeRanges(t, []string{"1-2"}).total())
|
||||||
|
assert.Equal(t, 91*time.Second, makeTimeRanges(t, []string{"1-2", "10-100"}).total())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue