github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/util/active_user_test.go (about) 1 package util 2 3 import ( 4 "fmt" 5 "runtime" 6 "strconv" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/require" 12 "go.uber.org/atomic" 13 ) 14 15 func TestActiveUser(t *testing.T) { 16 as := NewActiveUsers() 17 as.UpdateUserTimestamp("test1", 5) 18 as.UpdateUserTimestamp("test2", 10) 19 as.UpdateUserTimestamp("test3", 15) 20 21 require.Nil(t, as.PurgeInactiveUsers(2)) 22 require.Equal(t, []string{"test1"}, as.PurgeInactiveUsers(5)) 23 require.Nil(t, as.PurgeInactiveUsers(7)) 24 require.Equal(t, []string{"test2"}, as.PurgeInactiveUsers(12)) 25 26 as.UpdateUserTimestamp("test1", 17) 27 require.Equal(t, []string{"test3"}, as.PurgeInactiveUsers(16)) 28 require.Equal(t, []string{"test1"}, as.PurgeInactiveUsers(20)) 29 } 30 31 func TestActiveUserConcurrentUpdateAndPurge(t *testing.T) { 32 count := 10 33 34 as := NewActiveUsers() 35 36 done := sync.WaitGroup{} 37 stop := atomic.NewBool(false) 38 39 latestTS := atomic.NewInt64(0) 40 41 for j := 0; j < count; j++ { 42 done.Add(1) 43 44 go func() { 45 defer done.Done() 46 47 for !stop.Load() { 48 ts := latestTS.Inc() 49 50 // In each cycle, we update different user. 51 as.UpdateUserTimestamp(fmt.Sprintf("%d", ts), ts) 52 53 time.Sleep(1 * time.Millisecond) 54 } 55 }() 56 } 57 58 previousLatest := int64(0) 59 for i := 0; i < 10; i++ { 60 time.Sleep(100 * time.Millisecond) 61 62 latest := latestTS.Load() 63 require.True(t, latest > previousLatest) 64 65 previousLatest = latest 66 67 purged := as.PurgeInactiveUsers(latest) 68 require.NotEmpty(t, purged) 69 } 70 71 stop.Store(true) 72 done.Wait() 73 74 // Final purge. 75 latest := latestTS.Load() 76 as.PurgeInactiveUsers(latest) 77 78 // Purging again doesn't do anything. 79 purged := as.PurgeInactiveUsers(latest) 80 require.Empty(t, purged) 81 } 82 83 func BenchmarkActiveUsers_UpdateUserTimestamp(b *testing.B) { 84 for _, c := range []int{0, 5, 10, 25, 50, 100} { 85 b.Run(strconv.Itoa(c), func(b *testing.B) { 86 as := NewActiveUsers() 87 88 startGoroutinesDoingUpdates(b, c, as) 89 90 for i := 0; i < b.N; i++ { 91 as.UpdateUserTimestamp("test", int64(i)) 92 } 93 }) 94 } 95 } 96 97 func BenchmarkActiveUsers_Purge(b *testing.B) { 98 for _, c := range []int{0, 5, 10, 25, 50, 100} { 99 b.Run(strconv.Itoa(c), func(b *testing.B) { 100 as := NewActiveUsers() 101 102 startGoroutinesDoingUpdates(b, c, as) 103 104 for i := 0; i < b.N; i++ { 105 as.PurgeInactiveUsers(int64(i)) 106 } 107 }) 108 } 109 } 110 111 func startGoroutinesDoingUpdates(b *testing.B, count int, as *ActiveUsers) { 112 done := sync.WaitGroup{} 113 stop := atomic.NewBool(false) 114 115 started := sync.WaitGroup{} 116 for j := 0; j < count; j++ { 117 done.Add(1) 118 started.Add(1) 119 userID := fmt.Sprintf("user-%d", j) 120 go func() { 121 defer done.Done() 122 started.Done() 123 124 ts := int64(0) 125 for !stop.Load() { 126 ts++ 127 as.UpdateUserTimestamp(userID, ts) 128 129 // Give other goroutines a chance too. 130 if ts%1000 == 0 { 131 runtime.Gosched() 132 } 133 } 134 }() 135 } 136 started.Wait() 137 138 b.Cleanup(func() { 139 // Ask goroutines to stop, and then wait until they do. 140 stop.Store(true) 141 done.Wait() 142 }) 143 }