github.com/grafana/pyroscope@v1.18.0/pkg/usagestats/reporter_test.go (about) 1 package usagestats 2 3 import ( 4 "context" 5 "net/http" 6 "net/http/httptest" 7 "os" 8 "testing" 9 "time" 10 11 "github.com/go-kit/log" 12 "github.com/grafana/dskit/kv" 13 jsoniter "github.com/json-iterator/go" 14 "github.com/prometheus/client_golang/prometheus" 15 "github.com/stretchr/testify/require" 16 17 "github.com/grafana/pyroscope/pkg/objstore/client" 18 "github.com/grafana/pyroscope/pkg/objstore/providers/filesystem" 19 ) 20 21 func Test_LeaderElection(t *testing.T) { 22 stabilityCheckInterval = 100 * time.Millisecond 23 24 result := make(chan ClusterSeed, 10) 25 objectClient, err := client.NewBucket(context.Background(), client.Config{ 26 StorageBackendConfig: client.StorageBackendConfig{ 27 Backend: client.Filesystem, 28 Filesystem: filesystem.Config{ 29 Directory: t.TempDir(), 30 }, 31 }, 32 }, "test") 33 require.NoError(t, err) 34 for i := 0; i < 3; i++ { 35 go func() { 36 r, err := NewReporter(Config{Leader: true, Enabled: true}, kv.Config{ 37 Store: "inmemory", 38 }, objectClient, log.NewLogfmtLogger(os.Stdout), nil) 39 require.NoError(t, err) 40 r.init(context.Background()) 41 result <- r.cluster 42 }() 43 } 44 for i := 0; i < 7; i++ { 45 go func() { 46 r, err := NewReporter(Config{Leader: false, Enabled: true}, kv.Config{ 47 Store: "inmemory", 48 }, objectClient, log.NewLogfmtLogger(os.Stdout), nil) 49 require.NoError(t, err) 50 r.init(context.Background()) 51 result <- r.cluster 52 }() 53 } 54 55 var UID []string 56 for i := 0; i < 10; i++ { 57 cluster := <-result 58 require.NotNil(t, cluster) 59 UID = append(UID, cluster.UID) 60 } 61 first := UID[0] 62 for _, uid := range UID { 63 require.Equal(t, first, uid) 64 } 65 kvClient, err := kv.NewClient(kv.Config{Store: "inmemory"}, JSONCodec, nil, log.NewLogfmtLogger(os.Stdout)) 66 require.NoError(t, err) 67 // verify that the ID found is also correctly stored in the kv store and not overridden by another leader. 68 data, err := kvClient.Get(context.Background(), seedKey) 69 require.NoError(t, err) 70 require.Equal(t, data.(*ClusterSeed).UID, first) 71 } 72 73 func Test_ReportLoop(t *testing.T) { 74 // stub 75 reportCheckInterval = 100 * time.Millisecond 76 reportInterval = time.Second 77 stabilityCheckInterval = 100 * time.Millisecond 78 79 totalReport := 0 80 clusterIDs := []string{} 81 server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 82 var received Report 83 totalReport++ 84 require.NoError(t, jsoniter.NewDecoder(r.Body).Decode(&received)) 85 clusterIDs = append(clusterIDs, received.ClusterID) 86 rw.WriteHeader(http.StatusOK) 87 })) 88 usageStatsURL = server.URL 89 90 objectClient, err := client.NewBucket(context.Background(), client.Config{ 91 StorageBackendConfig: client.StorageBackendConfig{ 92 Backend: client.Filesystem, 93 Filesystem: filesystem.Config{ 94 Directory: t.TempDir(), 95 }, 96 }, 97 }, "test") 98 require.NoError(t, err) 99 100 r, err := NewReporter(Config{Leader: true, Enabled: true}, kv.Config{ 101 Store: "inmemory", 102 }, objectClient, log.NewLogfmtLogger(os.Stdout), prometheus.NewPedanticRegistry()) 103 require.NoError(t, err) 104 ctx, cancel := context.WithCancel(context.Background()) 105 r.initLeader(ctx) 106 107 go func() { 108 <-time.After(6*time.Second + (stabilityCheckInterval * time.Duration(stabilityMinimunRequired+1))) 109 cancel() 110 }() 111 require.Equal(t, nil, r.running(ctx)) 112 require.GreaterOrEqual(t, totalReport, 5) 113 first := clusterIDs[0] 114 for _, uid := range clusterIDs { 115 require.Equal(t, first, uid) 116 } 117 require.Equal(t, first, r.cluster.UID) 118 } 119 120 func Test_NextReport(t *testing.T) { 121 fixtures := map[string]struct { 122 interval time.Duration 123 createdAt time.Time 124 now time.Time 125 126 next time.Time 127 }{ 128 "createdAt aligned with interval and now": { 129 interval: 1 * time.Hour, 130 createdAt: time.Unix(0, time.Hour.Nanoseconds()), 131 now: time.Unix(0, 2*time.Hour.Nanoseconds()), 132 next: time.Unix(0, 2*time.Hour.Nanoseconds()), 133 }, 134 "createdAt aligned with interval": { 135 interval: 1 * time.Hour, 136 createdAt: time.Unix(0, time.Hour.Nanoseconds()), 137 now: time.Unix(0, 2*time.Hour.Nanoseconds()+1), 138 next: time.Unix(0, 3*time.Hour.Nanoseconds()), 139 }, 140 "createdAt not aligned": { 141 interval: 1 * time.Hour, 142 createdAt: time.Unix(0, time.Hour.Nanoseconds()+18*time.Minute.Nanoseconds()+20*time.Millisecond.Nanoseconds()), 143 now: time.Unix(0, 2*time.Hour.Nanoseconds()+1), 144 next: time.Unix(0, 2*time.Hour.Nanoseconds()+18*time.Minute.Nanoseconds()+20*time.Millisecond.Nanoseconds()), 145 }, 146 } 147 for name, f := range fixtures { 148 t.Run(name, func(t *testing.T) { 149 next := nextReport(f.interval, f.createdAt, f.now) 150 require.Equal(t, f.next, next) 151 }) 152 } 153 } 154 155 func TestWrongKV(t *testing.T) { 156 objectClient, err := client.NewBucket(context.Background(), client.Config{ 157 StorageBackendConfig: client.StorageBackendConfig{ 158 Backend: client.Filesystem, 159 Filesystem: filesystem.Config{ 160 Directory: t.TempDir(), 161 }, 162 }, 163 }, "test") 164 require.NoError(t, err) 165 166 r, err := NewReporter(Config{Leader: true, Enabled: true}, kv.Config{ 167 Store: "", 168 }, objectClient, log.NewLogfmtLogger(os.Stdout), prometheus.NewPedanticRegistry()) 169 require.NoError(t, err) 170 ctx, cancel := context.WithCancel(context.Background()) 171 go func() { 172 <-time.After(1 * time.Second) 173 cancel() 174 }() 175 require.Equal(t, nil, r.running(ctx)) 176 }