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 }