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  }