From f5617dadf3105899e95469185f52459bc90c5633 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 28 Aug 2018 11:17:05 +0100 Subject: [PATCH] fs/accounting: factor out eta and percent calculations and write tests --- fs/accounting/accounting.go | 19 +++----------- fs/accounting/stats.go | 37 +++++++++++++++++++++++++++ fs/accounting/stats_test.go | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 fs/accounting/stats_test.go diff --git a/fs/accounting/accounting.go b/fs/accounting/accounting.go index 3068b4253..a3746f5e9 100644 --- a/fs/accounting/accounting.go +++ b/fs/accounting/accounting.go @@ -229,26 +229,13 @@ func (acc *Account) speed() (bps, current float64) { // eta returns the ETA of the current operation, // rounded to full seconds. // If the ETA cannot be determined 'ok' returns false. -func (acc *Account) eta() (eta time.Duration, ok bool) { - if acc == nil || acc.size <= 0 { +func (acc *Account) eta() (etaDuration time.Duration, ok bool) { + if acc == nil { return 0, false } acc.statmu.Lock() defer acc.statmu.Unlock() - if acc.bytes == 0 { - return 0, false - } - left := acc.size - acc.bytes - if left <= 0 { - return 0, true - } - avg := acc.avg - if avg <= 0 { - return 0, false - } - seconds := float64(left) / avg - - return time.Second * time.Duration(seconds), true + return eta(acc.bytes, acc.size, acc.avg) } // String produces stats for this file diff --git a/fs/accounting/stats.go b/fs/accounting/stats.go index 1e795fd45..f7863ad29 100644 --- a/fs/accounting/stats.go +++ b/fs/accounting/stats.go @@ -140,6 +140,43 @@ func (s *StatsInfo) RemoteStats(in rc.Params) (out rc.Params, err error) { return out, nil } +// eta returns the ETA of the current operation, +// rounded to full seconds. +// If the ETA cannot be determined 'ok' returns false. +func eta(size, total int64, rate float64) (eta time.Duration, ok bool) { + if total <= 0 || size < 0 || rate <= 0 { + return 0, false + } + remaining := total - size + if remaining < 0 { + return 0, false + } + seconds := float64(remaining) / rate + return time.Second * time.Duration(seconds), true +} + +// etaString returns the ETA of the current operation, +// rounded to full seconds. +// If the ETA cannot be determined it returns "-" +func etaString(done, total int64, rate float64) string { + d, ok := eta(done, total, rate) + if !ok { + return "-" + } + return d.String() +} + +// percent returns a/b as a percentage rounded to the nearest integer +// as a string +// +// if the percentage is invalid it returns "-" +func percent(a int64, b int64) string { + if a < 0 || b <= 0 { + return "-" + } + return fmt.Sprintf("%d%%", int(float64(a)*100/float64(b)+0.5)) +} + // String convert the StatsInfo to a string for printing func (s *StatsInfo) String() string { s.mu.RLock() diff --git a/fs/accounting/stats_test.go b/fs/accounting/stats_test.go new file mode 100644 index 000000000..d7f5e852f --- /dev/null +++ b/fs/accounting/stats_test.go @@ -0,0 +1,51 @@ +package accounting + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestETA(t *testing.T) { + for _, test := range []struct { + size, total int64 + rate float64 + wantETA time.Duration + wantOK bool + wantString string + }{ + {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"}, + {size: -1, total: 100, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"}, + {size: 200, total: 100, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"}, + {size: 10, total: -1, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"}, + {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: "-"}, + } { + 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, test.wantOK, gotOK) + gotString := etaString(test.size, test.total, test.rate) + assert.Equal(t, test.wantString, gotString) + }) + } +} + +func TestPercentage(t *testing.T) { + assert.Equal(t, percent(0, 1000), "0%") + assert.Equal(t, percent(1, 1000), "0%") + assert.Equal(t, percent(9, 1000), "1%") + assert.Equal(t, percent(500, 1000), "50%") + assert.Equal(t, percent(1000, 1000), "100%") + assert.Equal(t, percent(1E8, 1E9), "10%") + assert.Equal(t, percent(1E8, 1E9), "10%") + assert.Equal(t, percent(0, 0), "-") + assert.Equal(t, percent(100, -100), "-") + assert.Equal(t, percent(-100, 100), "-") + assert.Equal(t, percent(-100, -100), "-") +}