github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fs/accounting/stats_test.go (about)

     1  package accounting
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/pkg/errors"
    10  	"github.com/rclone/rclone/fs"
    11  	"github.com/rclone/rclone/fs/fserrors"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestETA(t *testing.T) {
    17  	for _, test := range []struct {
    18  		size, total int64
    19  		rate        float64
    20  		wantETA     time.Duration
    21  		wantOK      bool
    22  		wantString  string
    23  	}{
    24  		// Custom String Cases
    25  		{size: 0, total: 365 * 86400, rate: 1.0, wantETA: 365 * 86400 * time.Second, wantOK: true, wantString: "1y"},
    26  		{size: 0, total: 7 * 86400, rate: 1.0, wantETA: 7 * 86400 * time.Second, wantOK: true, wantString: "1w"},
    27  		{size: 0, total: 1 * 86400, rate: 1.0, wantETA: 1 * 86400 * time.Second, wantOK: true, wantString: "1d"},
    28  		{size: 0, total: 1110 * 86400, rate: 1.0, wantETA: 1110 * 86400 * time.Second, wantOK: true, wantString: "3y2w1d"},
    29  		{size: 0, total: 15 * 86400, rate: 1.0, wantETA: 15 * 86400 * time.Second, wantOK: true, wantString: "2w1d"},
    30  		// Composite Custom String Cases
    31  		{size: 0, total: 1.5 * 86400, rate: 1.0, wantETA: 1.5 * 86400 * time.Second, wantOK: true, wantString: "1d12h"},
    32  		{size: 0, total: 95000, rate: 1.0, wantETA: 95000 * time.Second, wantOK: true, wantString: "1d2h23m20s"},
    33  		// Standard Duration String Cases
    34  		{size: 0, total: 100, rate: 1.0, wantETA: 100 * time.Second, wantOK: true, wantString: "1m40s"},
    35  		{size: 50, total: 100, rate: 1.0, wantETA: 50 * time.Second, wantOK: true, wantString: "50s"},
    36  		{size: 100, total: 100, rate: 1.0, wantETA: 0 * time.Second, wantOK: true, wantString: "0s"},
    37  		// No String Cases
    38  		{size: -1, total: 100, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"},
    39  		{size: 200, total: 100, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"},
    40  		{size: 10, total: -1, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"},
    41  		{size: 10, total: 20, rate: 0.0, wantETA: 0, wantOK: false, wantString: "-"},
    42  		{size: 10, total: 20, rate: -1.0, wantETA: 0, wantOK: false, wantString: "-"},
    43  		{size: 0, total: 0, rate: 1.0, wantETA: 0, wantOK: false, wantString: "-"},
    44  	} {
    45  		t.Run(fmt.Sprintf("size=%d/total=%d/rate=%f", test.size, test.total, test.rate), func(t *testing.T) {
    46  			gotETA, gotOK := eta(test.size, test.total, test.rate)
    47  			assert.Equal(t, test.wantETA, gotETA)
    48  			assert.Equal(t, test.wantOK, gotOK)
    49  			gotString := etaString(test.size, test.total, test.rate)
    50  			assert.Equal(t, test.wantString, gotString)
    51  		})
    52  	}
    53  }
    54  
    55  func TestPercentage(t *testing.T) {
    56  	assert.Equal(t, percent(0, 1000), "0%")
    57  	assert.Equal(t, percent(1, 1000), "0%")
    58  	assert.Equal(t, percent(9, 1000), "1%")
    59  	assert.Equal(t, percent(500, 1000), "50%")
    60  	assert.Equal(t, percent(1000, 1000), "100%")
    61  	assert.Equal(t, percent(1e8, 1e9), "10%")
    62  	assert.Equal(t, percent(1e8, 1e9), "10%")
    63  	assert.Equal(t, percent(0, 0), "-")
    64  	assert.Equal(t, percent(100, -100), "-")
    65  	assert.Equal(t, percent(-100, 100), "-")
    66  	assert.Equal(t, percent(-100, -100), "-")
    67  }
    68  
    69  func TestStatsError(t *testing.T) {
    70  	s := NewStats()
    71  	assert.Equal(t, int64(0), s.GetErrors())
    72  	assert.False(t, s.HadFatalError())
    73  	assert.False(t, s.HadRetryError())
    74  	assert.Equal(t, time.Time{}, s.RetryAfter())
    75  	assert.Equal(t, nil, s.GetLastError())
    76  	assert.False(t, s.Errored())
    77  
    78  	t0 := time.Now()
    79  	t1 := t0.Add(time.Second)
    80  
    81  	_ = s.Error(nil)
    82  	assert.Equal(t, int64(0), s.GetErrors())
    83  	assert.False(t, s.HadFatalError())
    84  	assert.False(t, s.HadRetryError())
    85  	assert.Equal(t, time.Time{}, s.RetryAfter())
    86  	assert.Equal(t, nil, s.GetLastError())
    87  	assert.False(t, s.Errored())
    88  
    89  	_ = s.Error(io.EOF)
    90  	assert.Equal(t, int64(1), s.GetErrors())
    91  	assert.False(t, s.HadFatalError())
    92  	assert.True(t, s.HadRetryError())
    93  	assert.Equal(t, time.Time{}, s.RetryAfter())
    94  	assert.Equal(t, io.EOF, s.GetLastError())
    95  	assert.True(t, s.Errored())
    96  
    97  	e := fserrors.ErrorRetryAfter(t0)
    98  	_ = s.Error(e)
    99  	assert.Equal(t, int64(2), s.GetErrors())
   100  	assert.False(t, s.HadFatalError())
   101  	assert.True(t, s.HadRetryError())
   102  	assert.Equal(t, t0, s.RetryAfter())
   103  	assert.Equal(t, e, s.GetLastError())
   104  
   105  	err := errors.Wrap(fserrors.ErrorRetryAfter(t1), "potato")
   106  	err = s.Error(err)
   107  	assert.Equal(t, int64(3), s.GetErrors())
   108  	assert.False(t, s.HadFatalError())
   109  	assert.True(t, s.HadRetryError())
   110  	assert.Equal(t, t1, s.RetryAfter())
   111  	assert.Equal(t, t1, fserrors.RetryAfterErrorTime(err))
   112  
   113  	_ = s.Error(fserrors.FatalError(io.EOF))
   114  	assert.Equal(t, int64(4), s.GetErrors())
   115  	assert.True(t, s.HadFatalError())
   116  	assert.True(t, s.HadRetryError())
   117  	assert.Equal(t, t1, s.RetryAfter())
   118  
   119  	s.ResetErrors()
   120  	assert.Equal(t, int64(0), s.GetErrors())
   121  	assert.False(t, s.HadFatalError())
   122  	assert.False(t, s.HadRetryError())
   123  	assert.Equal(t, time.Time{}, s.RetryAfter())
   124  	assert.Equal(t, nil, s.GetLastError())
   125  	assert.False(t, s.Errored())
   126  
   127  	_ = s.Error(fserrors.NoRetryError(io.EOF))
   128  	assert.Equal(t, int64(1), s.GetErrors())
   129  	assert.False(t, s.HadFatalError())
   130  	assert.False(t, s.HadRetryError())
   131  	assert.Equal(t, time.Time{}, s.RetryAfter())
   132  }
   133  
   134  func TestStatsTotalDuration(t *testing.T) {
   135  	startTime := time.Now()
   136  	time1 := startTime.Add(-40 * time.Second)
   137  	time2 := time1.Add(10 * time.Second)
   138  	time3 := time2.Add(10 * time.Second)
   139  	time4 := time3.Add(10 * time.Second)
   140  
   141  	t.Run("Single completed transfer", func(t *testing.T) {
   142  		s := NewStats()
   143  		tr1 := &Transfer{
   144  			startedAt:   time1,
   145  			completedAt: time2,
   146  		}
   147  		s.AddTransfer(tr1)
   148  
   149  		s.mu.Lock()
   150  		total := s.totalDuration()
   151  		s.mu.Unlock()
   152  
   153  		assert.Equal(t, 1, len(s.startedTransfers))
   154  		assert.Equal(t, 10*time.Second, total)
   155  		s.RemoveTransfer(tr1)
   156  		assert.Equal(t, 10*time.Second, total)
   157  		assert.Equal(t, 0, len(s.startedTransfers))
   158  	})
   159  
   160  	t.Run("Single uncompleted transfer", func(t *testing.T) {
   161  		s := NewStats()
   162  		tr1 := &Transfer{
   163  			startedAt: time1,
   164  		}
   165  		s.AddTransfer(tr1)
   166  
   167  		s.mu.Lock()
   168  		total := s.totalDuration()
   169  		s.mu.Unlock()
   170  
   171  		assert.Equal(t, time.Since(time1)/time.Second, total/time.Second)
   172  		s.RemoveTransfer(tr1)
   173  		assert.Equal(t, time.Since(time1)/time.Second, total/time.Second)
   174  	})
   175  
   176  	t.Run("Overlapping without ending", func(t *testing.T) {
   177  		s := NewStats()
   178  		tr1 := &Transfer{
   179  			startedAt:   time2,
   180  			completedAt: time3,
   181  		}
   182  		s.AddTransfer(tr1)
   183  		tr2 := &Transfer{
   184  			startedAt:   time2,
   185  			completedAt: time2.Add(time.Second),
   186  		}
   187  		s.AddTransfer(tr2)
   188  		tr3 := &Transfer{
   189  			startedAt:   time1,
   190  			completedAt: time3,
   191  		}
   192  		s.AddTransfer(tr3)
   193  		tr4 := &Transfer{
   194  			startedAt:   time3,
   195  			completedAt: time4,
   196  		}
   197  		s.AddTransfer(tr4)
   198  		tr5 := &Transfer{
   199  			startedAt: time.Now(),
   200  		}
   201  		s.AddTransfer(tr5)
   202  
   203  		time.Sleep(time.Millisecond)
   204  
   205  		s.mu.Lock()
   206  		total := s.totalDuration()
   207  		s.mu.Unlock()
   208  
   209  		assert.Equal(t, time.Duration(30), total/time.Second)
   210  		s.RemoveTransfer(tr1)
   211  		assert.Equal(t, time.Duration(30), total/time.Second)
   212  		s.RemoveTransfer(tr2)
   213  		assert.Equal(t, time.Duration(30), total/time.Second)
   214  		s.RemoveTransfer(tr3)
   215  		assert.Equal(t, time.Duration(30), total/time.Second)
   216  		s.RemoveTransfer(tr4)
   217  		assert.Equal(t, time.Duration(30), total/time.Second)
   218  	})
   219  
   220  	t.Run("Mixed completed and uncompleted transfers", func(t *testing.T) {
   221  		s := NewStats()
   222  		s.AddTransfer(&Transfer{
   223  			startedAt:   time1,
   224  			completedAt: time2,
   225  		})
   226  		s.AddTransfer(&Transfer{
   227  			startedAt: time2,
   228  		})
   229  		s.AddTransfer(&Transfer{
   230  			startedAt: time3,
   231  		})
   232  		s.AddTransfer(&Transfer{
   233  			startedAt: time3,
   234  		})
   235  
   236  		s.mu.Lock()
   237  		total := s.totalDuration()
   238  		s.mu.Unlock()
   239  
   240  		assert.Equal(t, startTime.Sub(time1)/time.Second, total/time.Second)
   241  	})
   242  }
   243  
   244  // make time ranges from string description for testing
   245  func makeTimeRanges(t *testing.T, in []string) timeRanges {
   246  	trs := make(timeRanges, len(in))
   247  	for i, Range := range in {
   248  		var start, end int64
   249  		n, err := fmt.Sscanf(Range, "%d-%d", &start, &end)
   250  		require.NoError(t, err)
   251  		require.Equal(t, 2, n)
   252  		trs[i] = timeRange{time.Unix(start, 0), time.Unix(end, 0)}
   253  	}
   254  	return trs
   255  }
   256  
   257  func (trs timeRanges) toStrings() (out []string) {
   258  	out = []string{}
   259  	for _, tr := range trs {
   260  		out = append(out, fmt.Sprintf("%d-%d", tr.start.Unix(), tr.end.Unix()))
   261  	}
   262  	return out
   263  }
   264  
   265  func TestTimeRangeMerge(t *testing.T) {
   266  	for _, test := range []struct {
   267  		in   []string
   268  		want []string
   269  	}{{
   270  		in:   []string{},
   271  		want: []string{},
   272  	}, {
   273  		in:   []string{"1-2"},
   274  		want: []string{"1-2"},
   275  	}, {
   276  		in:   []string{"1-4", "2-3"},
   277  		want: []string{"1-4"},
   278  	}, {
   279  		in:   []string{"2-3", "1-4"},
   280  		want: []string{"1-4"},
   281  	}, {
   282  		in:   []string{"1-3", "2-4"},
   283  		want: []string{"1-4"},
   284  	}, {
   285  		in:   []string{"2-4", "1-3"},
   286  		want: []string{"1-4"},
   287  	}, {
   288  		in:   []string{"1-2", "2-3"},
   289  		want: []string{"1-3"},
   290  	}, {
   291  		in:   []string{"2-3", "1-2"},
   292  		want: []string{"1-3"},
   293  	}, {
   294  		in:   []string{"1-2", "3-4"},
   295  		want: []string{"1-2", "3-4"},
   296  	}, {
   297  		in:   []string{"1-3", "7-8", "4-6", "2-5", "7-8", "7-8"},
   298  		want: []string{"1-6", "7-8"},
   299  	}} {
   300  
   301  		in := makeTimeRanges(t, test.in)
   302  		in.merge()
   303  
   304  		got := in.toStrings()
   305  		assert.Equal(t, test.want, got)
   306  	}
   307  }
   308  
   309  func TestTimeRangeCull(t *testing.T) {
   310  	for _, test := range []struct {
   311  		in           []string
   312  		cutoff       int64
   313  		want         []string
   314  		wantDuration time.Duration
   315  	}{{
   316  		in:           []string{},
   317  		cutoff:       1,
   318  		want:         []string{},
   319  		wantDuration: 0 * time.Second,
   320  	}, {
   321  		in:           []string{"1-2"},
   322  		cutoff:       1,
   323  		want:         []string{"1-2"},
   324  		wantDuration: 0 * time.Second,
   325  	}, {
   326  		in:           []string{"2-5", "7-9"},
   327  		cutoff:       1,
   328  		want:         []string{"2-5", "7-9"},
   329  		wantDuration: 0 * time.Second,
   330  	}, {
   331  		in:           []string{"2-5", "7-9"},
   332  		cutoff:       4,
   333  		want:         []string{"2-5", "7-9"},
   334  		wantDuration: 0 * time.Second,
   335  	}, {
   336  		in:           []string{"2-5", "7-9"},
   337  		cutoff:       5,
   338  		want:         []string{"7-9"},
   339  		wantDuration: 3 * time.Second,
   340  	}, {
   341  		in:           []string{"2-5", "7-9", "2-5", "2-5"},
   342  		cutoff:       6,
   343  		want:         []string{"7-9"},
   344  		wantDuration: 9 * time.Second,
   345  	}, {
   346  		in:           []string{"7-9", "3-3", "2-5"},
   347  		cutoff:       7,
   348  		want:         []string{"7-9"},
   349  		wantDuration: 3 * time.Second,
   350  	}, {
   351  		in:           []string{"2-5", "7-9"},
   352  		cutoff:       8,
   353  		want:         []string{"7-9"},
   354  		wantDuration: 3 * time.Second,
   355  	}, {
   356  		in:           []string{"2-5", "7-9"},
   357  		cutoff:       9,
   358  		want:         []string{},
   359  		wantDuration: 5 * time.Second,
   360  	}, {
   361  		in:           []string{"2-5", "7-9"},
   362  		cutoff:       10,
   363  		want:         []string{},
   364  		wantDuration: 5 * time.Second,
   365  	}} {
   366  
   367  		in := makeTimeRanges(t, test.in)
   368  		cutoff := time.Unix(test.cutoff, 0)
   369  		gotDuration := in.cull(cutoff)
   370  
   371  		what := fmt.Sprintf("in=%q, cutoff=%d", test.in, test.cutoff)
   372  		got := in.toStrings()
   373  		assert.Equal(t, test.want, got, what)
   374  		assert.Equal(t, test.wantDuration, gotDuration, what)
   375  	}
   376  }
   377  
   378  func TestTimeRangeDuration(t *testing.T) {
   379  	assert.Equal(t, 0*time.Second, timeRanges{}.total())
   380  	assert.Equal(t, 1*time.Second, makeTimeRanges(t, []string{"1-2"}).total())
   381  	assert.Equal(t, 91*time.Second, makeTimeRanges(t, []string{"1-2", "10-100"}).total())
   382  }
   383  
   384  func TestPruneTransfers(t *testing.T) {
   385  	for _, test := range []struct {
   386  		Name                     string
   387  		Transfers                int
   388  		Limit                    int
   389  		ExpectedStartedTransfers int
   390  	}{
   391  		{
   392  			Name:                     "Limited number of StartedTransfers",
   393  			Limit:                    100,
   394  			Transfers:                200,
   395  			ExpectedStartedTransfers: 100 + fs.Config.Transfers,
   396  		},
   397  		{
   398  			Name:                     "Unlimited number of StartedTransfers",
   399  			Limit:                    -1,
   400  			Transfers:                200,
   401  			ExpectedStartedTransfers: 200,
   402  		},
   403  	} {
   404  		t.Run(test.Name, func(t *testing.T) {
   405  			prevLimit := MaxCompletedTransfers
   406  			MaxCompletedTransfers = test.Limit
   407  			defer func() { MaxCompletedTransfers = prevLimit }()
   408  
   409  			s := NewStats()
   410  			for i := int64(1); i <= int64(test.Transfers); i++ {
   411  				s.AddTransfer(&Transfer{
   412  					startedAt:   time.Unix(i, 0),
   413  					completedAt: time.Unix(i+1, 0),
   414  				})
   415  			}
   416  
   417  			s.mu.Lock()
   418  			assert.Equal(t, time.Duration(test.Transfers)*time.Second, s.totalDuration())
   419  			assert.Equal(t, test.Transfers, len(s.startedTransfers))
   420  			s.mu.Unlock()
   421  
   422  			for i := 0; i < test.Transfers; i++ {
   423  				s.PruneTransfers()
   424  			}
   425  
   426  			s.mu.Lock()
   427  			assert.Equal(t, time.Duration(test.Transfers)*time.Second, s.totalDuration())
   428  			assert.Equal(t, test.ExpectedStartedTransfers, len(s.startedTransfers))
   429  			s.mu.Unlock()
   430  
   431  		})
   432  	}
   433  }