accounting: avoid negative ETA values for very slow speeds

Integer overflow would lead to ETA such as "-255y7w4h11m22s966ms",
as reported in #6381. Now the value will be clipped at the maximum
"292y24w3d23h47m16s", and it will be shown as infinity.
This commit is contained in:
albertony 2022-08-19 10:21:00 +02:00
parent 120cfcde70
commit 67132ecaec
2 changed files with 26 additions and 4 deletions

View File

@ -229,6 +229,11 @@ func (s *StatsInfo) totalDuration() time.Duration {
return s.oldDuration + timeRanges.total()
}
const (
etaMaxSeconds = (1<<63 - 1) / int64(time.Second) // Largest possible ETA as number of seconds
etaMax = time.Duration(etaMaxSeconds) * time.Second // Largest possible ETA, which is in second precision, representing "292y24w3d23h47m16s"
)
// eta returns the ETA of the current operation,
// rounded to full seconds.
// If the ETA cannot be determined 'ok' returns false.
@ -240,11 +245,17 @@ func eta(size, total int64, rate float64) (eta time.Duration, ok bool) {
if remaining < 0 {
return 0, false
}
seconds := float64(remaining) / rate
seconds := int64(float64(remaining) / rate)
if seconds < 0 {
seconds = 0
// Got Int64 overflow
eta = etaMax
} else if seconds >= etaMaxSeconds {
// Would get Int64 overflow if converting from seconds to Duration (nanoseconds)
eta = etaMax
} else {
eta = time.Duration(seconds) * time.Second
}
return time.Second * time.Duration(seconds), true
return eta, true
}
// etaString returns the ETA of the current operation,
@ -255,6 +266,9 @@ func etaString(done, total int64, rate float64) string {
if !ok {
return "-"
}
if d == etaMax {
return "-"
}
return fs.Duration(d).ReadableString()
}

View File

@ -31,6 +31,9 @@ func TestETA(t *testing.T) {
{size: 0, total: 1.5 * 86400, rate: 1.0, wantETA: 1.5 * 86400 * time.Second, wantOK: true, wantString: "1d12h"},
{size: 0, total: 95000, rate: 1.0, wantETA: 95000 * time.Second, wantOK: true, wantString: "1d2h23m20s"},
// Standard Duration String Cases
{size: 0, total: 1, rate: 2.0, wantETA: 0, wantOK: true, wantString: "0s"},
{size: 0, total: 1, rate: 1.0, wantETA: time.Second, wantOK: true, wantString: "1s"},
{size: 0, total: 1, rate: 0.5, wantETA: 2 * time.Second, wantOK: true, wantString: "2s"},
{size: 0, total: 100, rate: 1.0, wantETA: 100 * time.Second, wantOK: true, wantString: "1m40s"},
{size: 50, total: 100, rate: 1.0, wantETA: 50 * time.Second, wantOK: true, wantString: "50s"},
{size: 100, total: 100, rate: 1.0, wantETA: 0 * time.Second, wantOK: true, wantString: "0s"},
@ -41,10 +44,15 @@ func TestETA(t *testing.T) {
{size: 10, total: 20, rate: 0.0, wantETA: 0, wantOK: false, wantString: "-"},
{size: 10, total: 20, rate: -1.0, wantETA: 0, wantOK: false, wantString: "-"},
{size: 0, total: 0, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"},
// Extreme Cases
{size: 0, total: (1 << 63) - 1, rate: 1.0, wantETA: (time.Duration((1<<63)-1) / time.Second) * time.Second, wantOK: true, wantString: "-"},
{size: 0, total: ((1 << 63) - 1) / int64(time.Second), rate: 1.0, wantETA: (time.Duration((1<<63)-1) / time.Second) * time.Second, wantOK: true, wantString: "-"},
{size: 0, total: ((1<<63)-1)/int64(time.Second) - 1, rate: 1.0, wantETA: (time.Duration((1<<63)-1)/time.Second - 1) * time.Second, wantOK: true, wantString: "292y24w3d23h47m15s"},
{size: 0, total: ((1<<63)-1)/int64(time.Second) - 1, rate: 0.1, wantETA: (time.Duration((1<<63)-1) / time.Second) * time.Second, wantOK: true, wantString: "-"},
} {
t.Run(fmt.Sprintf("size=%d/total=%d/rate=%f", test.size, test.total, test.rate), func(t *testing.T) {
gotETA, gotOK := eta(test.size, test.total, test.rate)
assert.Equal(t, test.wantETA, gotETA)
assert.Equal(t, int64(test.wantETA), int64(gotETA))
assert.Equal(t, test.wantOK, gotOK)
gotString := etaString(test.size, test.total, test.rate)
assert.Equal(t, test.wantString, gotString)