github.com/divyam234/rclone@v1.64.1/fs/accounting/stats_groups_test.go (about)

     1  package accounting
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"runtime"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/divyam234/rclone/fs/rc"
    11  	"github.com/divyam234/rclone/fstest/testy"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestStatsGroupOperations(t *testing.T) {
    17  	ctx := context.Background()
    18  
    19  	t.Run("empty group returns nil", func(t *testing.T) {
    20  		t.Parallel()
    21  		sg := newStatsGroups()
    22  		sg.get("invalid-group")
    23  	})
    24  
    25  	t.Run("set assigns stats to group", func(t *testing.T) {
    26  		t.Parallel()
    27  		stats := NewStats(ctx)
    28  		sg := newStatsGroups()
    29  		sg.set(ctx, "test", stats)
    30  		sg.set(ctx, "test1", stats)
    31  		if len(sg.m) != len(sg.names()) || len(sg.m) != 2 {
    32  			t.Fatalf("Expected two stats got %d, %d", len(sg.m), len(sg.order))
    33  		}
    34  	})
    35  
    36  	t.Run("get returns correct group", func(t *testing.T) {
    37  		t.Parallel()
    38  		stats := NewStats(ctx)
    39  		sg := newStatsGroups()
    40  		sg.set(ctx, "test", stats)
    41  		sg.set(ctx, "test1", stats)
    42  		got := sg.get("test")
    43  		if got != stats {
    44  			t.Fatal("get returns incorrect stats")
    45  		}
    46  	})
    47  
    48  	t.Run("sum returns correct values", func(t *testing.T) {
    49  		t.Parallel()
    50  		stats1 := NewStats(ctx)
    51  		stats1.bytes = 5
    52  		stats1.transferQueueSize = 10
    53  		stats1.errors = 6
    54  		stats1.oldDuration = time.Second
    55  		stats1.oldTimeRanges = []timeRange{{time.Now(), time.Now().Add(time.Second)}}
    56  		stats2 := NewStats(ctx)
    57  		stats2.bytes = 10
    58  		stats2.errors = 12
    59  		stats1.transferQueueSize = 20
    60  		stats2.oldDuration = 2 * time.Second
    61  		stats2.oldTimeRanges = []timeRange{{time.Now(), time.Now().Add(2 * time.Second)}}
    62  		sg := newStatsGroups()
    63  		sg.set(ctx, "test1", stats1)
    64  		sg.set(ctx, "test2", stats2)
    65  		sum := sg.sum(ctx)
    66  		assert.Equal(t, stats1.bytes+stats2.bytes, sum.bytes)
    67  		assert.Equal(t, stats1.transferQueueSize+stats2.transferQueueSize, sum.transferQueueSize)
    68  		assert.Equal(t, stats1.errors+stats2.errors, sum.errors)
    69  		assert.Equal(t, stats1.oldDuration+stats2.oldDuration, sum.oldDuration)
    70  		assert.Equal(t, stats1.average.speed+stats2.average.speed, sum.average.speed)
    71  		// dict can iterate in either order
    72  		a := timeRanges{stats1.oldTimeRanges[0], stats2.oldTimeRanges[0]}
    73  		b := timeRanges{stats2.oldTimeRanges[0], stats1.oldTimeRanges[0]}
    74  		if !assert.ObjectsAreEqual(a, sum.oldTimeRanges) {
    75  			assert.Equal(t, b, sum.oldTimeRanges)
    76  		}
    77  	})
    78  
    79  	t.Run("delete removes stats", func(t *testing.T) {
    80  		t.Parallel()
    81  		stats := NewStats(ctx)
    82  		sg := newStatsGroups()
    83  		sg.set(ctx, "test", stats)
    84  		sg.set(ctx, "test1", stats)
    85  		sg.delete("test1")
    86  		if sg.get("test1") != nil {
    87  			t.Fatal("stats not deleted")
    88  		}
    89  		if len(sg.m) != len(sg.names()) || len(sg.m) != 1 {
    90  			t.Fatalf("Expected two stats got %d, %d", len(sg.m), len(sg.order))
    91  		}
    92  	})
    93  
    94  	t.Run("memory is reclaimed", func(t *testing.T) {
    95  		testy.SkipUnreliable(t)
    96  		var (
    97  			count      = 1000
    98  			start, end runtime.MemStats
    99  			sg         = newStatsGroups()
   100  		)
   101  
   102  		runtime.GC()
   103  		runtime.ReadMemStats(&start)
   104  
   105  		for i := 0; i < count; i++ {
   106  			sg.set(ctx, fmt.Sprintf("test-%d", i), NewStats(ctx))
   107  		}
   108  
   109  		for i := 0; i < count; i++ {
   110  			sg.delete(fmt.Sprintf("test-%d", i))
   111  		}
   112  
   113  		runtime.GC()
   114  		runtime.ReadMemStats(&end)
   115  
   116  		t.Logf("%+v\n%+v", start, end)
   117  		diff := percentDiff(start.HeapObjects, end.HeapObjects)
   118  		if diff > 1 {
   119  			t.Errorf("HeapObjects = %d, expected %d", end.HeapObjects, start.HeapObjects)
   120  		}
   121  	})
   122  
   123  	testGroupStatsInfo := NewStatsGroup(ctx, "test-group")
   124  	require.NoError(t, testGroupStatsInfo.DeleteFile(ctx, 0))
   125  	for i := 0; i < 41; i++ {
   126  		require.NoError(t, GlobalStats().DeleteFile(ctx, 0))
   127  	}
   128  
   129  	t.Run("core/group-list", func(t *testing.T) {
   130  		call := rc.Calls.Get("core/group-list")
   131  		require.NotNil(t, call)
   132  		got, err := call.Fn(ctx, rc.Params{})
   133  		require.NoError(t, err)
   134  		require.Equal(t, rc.Params{
   135  			"groups": []string{
   136  				"test-group",
   137  			},
   138  		}, got)
   139  	})
   140  
   141  	t.Run("core/stats", func(t *testing.T) {
   142  		call := rc.Calls.Get("core/stats")
   143  		require.NotNil(t, call)
   144  		gotNoGroup, err := call.Fn(ctx, rc.Params{})
   145  		require.NoError(t, err)
   146  		gotGroup, err := call.Fn(ctx, rc.Params{"group": "test-group"})
   147  		require.NoError(t, err)
   148  		assert.Equal(t, int64(42), gotNoGroup["deletes"])
   149  		assert.Equal(t, int64(1), gotGroup["deletes"])
   150  	})
   151  
   152  	t.Run("core/transferred", func(t *testing.T) {
   153  		call := rc.Calls.Get("core/transferred")
   154  		require.NotNil(t, call)
   155  		gotNoGroup, err := call.Fn(ctx, rc.Params{})
   156  		require.NoError(t, err)
   157  		gotGroup, err := call.Fn(ctx, rc.Params{"group": "test-group"})
   158  		require.NoError(t, err)
   159  		assert.Equal(t, rc.Params{
   160  			"transferred": []TransferSnapshot{},
   161  		}, gotNoGroup)
   162  		assert.Equal(t, rc.Params{
   163  			"transferred": []TransferSnapshot{},
   164  		}, gotGroup)
   165  	})
   166  
   167  	t.Run("core/stats-reset", func(t *testing.T) {
   168  		call := rc.Calls.Get("core/stats-reset")
   169  		require.NotNil(t, call)
   170  
   171  		assert.Equal(t, int64(41), GlobalStats().deletes)
   172  		assert.Equal(t, int64(1), testGroupStatsInfo.deletes)
   173  
   174  		_, err := call.Fn(ctx, rc.Params{"group": "test-group"})
   175  		require.NoError(t, err)
   176  
   177  		assert.Equal(t, int64(41), GlobalStats().deletes)
   178  		assert.Equal(t, int64(0), testGroupStatsInfo.deletes)
   179  
   180  		_, err = call.Fn(ctx, rc.Params{})
   181  		require.NoError(t, err)
   182  
   183  		assert.Equal(t, int64(0), GlobalStats().deletes)
   184  		assert.Equal(t, int64(0), testGroupStatsInfo.deletes)
   185  
   186  		_, err = call.Fn(ctx, rc.Params{"group": "not-found"})
   187  		require.ErrorContains(t, err, `group "not-found" not found`)
   188  
   189  	})
   190  
   191  	testGroupStatsInfo = NewStatsGroup(ctx, "test-group")
   192  
   193  	t.Run("core/stats-delete", func(t *testing.T) {
   194  		call := rc.Calls.Get("core/stats-delete")
   195  		require.NotNil(t, call)
   196  
   197  		assert.Equal(t, []string{"test-group"}, groups.names())
   198  
   199  		_, err := call.Fn(ctx, rc.Params{"group": "test-group"})
   200  		require.NoError(t, err)
   201  
   202  		assert.Equal(t, []string{}, groups.names())
   203  
   204  		_, err = call.Fn(ctx, rc.Params{"group": "not-found"})
   205  		require.NoError(t, err)
   206  	})
   207  }
   208  
   209  func percentDiff(start, end uint64) uint64 {
   210  	return (start - end) * 100 / start
   211  }