github.com/grafana/pyroscope@v1.18.0/pkg/ingester/ingester_test.go (about)

     1  package ingester
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime/pprof"
     9  	"testing"
    10  	"time"
    11  
    12  	"connectrpc.com/connect"
    13  	"github.com/go-kit/log"
    14  	"github.com/google/uuid"
    15  	"github.com/grafana/dskit/flagext"
    16  	"github.com/grafana/dskit/kv"
    17  	"github.com/grafana/dskit/ring"
    18  	"github.com/grafana/dskit/services"
    19  	"github.com/prometheus/client_golang/prometheus"
    20  	"github.com/stretchr/testify/require"
    21  
    22  	pushv1 "github.com/grafana/pyroscope/api/gen/proto/go/push/v1"
    23  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    24  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    25  	"github.com/grafana/pyroscope/pkg/objstore/client"
    26  	"github.com/grafana/pyroscope/pkg/objstore/providers/filesystem"
    27  	"github.com/grafana/pyroscope/pkg/phlaredb"
    28  	phlarecontext "github.com/grafana/pyroscope/pkg/pyroscope/context"
    29  	"github.com/grafana/pyroscope/pkg/tenant"
    30  )
    31  
    32  func defaultIngesterTestConfig(t testing.TB) Config {
    33  	kvClient, err := kv.NewClient(kv.Config{Store: "inmemory"}, ring.GetCodec(), nil, log.NewNopLogger())
    34  	require.NoError(t, err)
    35  	cfg := Config{}
    36  	flagext.DefaultValues(&cfg)
    37  	cfg.LifecyclerConfig.RingConfig.KVStore.Mock = kvClient
    38  	cfg.LifecyclerConfig.NumTokens = 1
    39  	cfg.LifecyclerConfig.ListenPort = 0
    40  	cfg.LifecyclerConfig.Addr = "localhost"
    41  	cfg.LifecyclerConfig.ID = "localhost"
    42  	cfg.LifecyclerConfig.FinalSleep = 0
    43  	cfg.LifecyclerConfig.MinReadyDuration = 0
    44  	return cfg
    45  }
    46  
    47  func testProfile(t *testing.T) []byte {
    48  	t.Helper()
    49  
    50  	buf := bytes.NewBuffer(nil)
    51  	require.NoError(t, pprof.WriteHeapProfile(buf))
    52  	return buf.Bytes()
    53  }
    54  
    55  func Test_MultitenantReadWrite(t *testing.T) {
    56  	dbPath := t.TempDir()
    57  	logger := log.NewJSONLogger(os.Stdout)
    58  	reg := prometheus.NewRegistry()
    59  	ctx := phlarecontext.WithLogger(context.Background(), logger)
    60  	ctx = phlarecontext.WithRegistry(ctx, reg)
    61  	cfg := client.Config{
    62  		StorageBackendConfig: client.StorageBackendConfig{
    63  			Backend: client.Filesystem,
    64  			Filesystem: filesystem.Config{
    65  				Directory: dbPath,
    66  			},
    67  		},
    68  	}
    69  
    70  	fs, err := client.NewBucket(ctx, cfg, "storage")
    71  	require.NoError(t, err)
    72  
    73  	ing, err := New(ctx, defaultIngesterTestConfig(t), phlaredb.Config{
    74  		DataPath:         dbPath,
    75  		MaxBlockDuration: 30 * time.Hour,
    76  	}, fs, &fakeLimits{}, 0)
    77  	require.NoError(t, err)
    78  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing))
    79  
    80  	req := &connect.Request[pushv1.PushRequest]{
    81  		Msg: &pushv1.PushRequest{
    82  			Series: []*pushv1.RawProfileSeries{
    83  				{
    84  					Samples: []*pushv1.RawSample{
    85  						{
    86  							ID:         uuid.NewString(),
    87  							RawProfile: testProfile(t),
    88  						},
    89  					},
    90  				},
    91  			},
    92  		},
    93  	}
    94  	req.Msg.Series[0].Labels = phlaremodel.LabelsFromStrings("foo", "bar")
    95  	_, err = ing.Push(tenant.InjectTenantID(context.Background(), "foo"), req)
    96  	require.NoError(t, err)
    97  
    98  	req.Msg.Series[0].Labels = phlaremodel.LabelsFromStrings("buzz", "bazz")
    99  	_, err = ing.Push(tenant.InjectTenantID(context.Background(), "buzz"), req)
   100  	require.NoError(t, err)
   101  
   102  	labelNames, err := ing.LabelNames(tenant.InjectTenantID(context.Background(), "foo"), connect.NewRequest(&typesv1.LabelNamesRequest{}))
   103  	require.NoError(t, err)
   104  	require.Equal(t, []string{"__period_type__", "__period_unit__", "__profile_type__", "__type__", "__unit__", "foo"}, labelNames.Msg.Names)
   105  
   106  	labelNames, err = ing.LabelNames(tenant.InjectTenantID(context.Background(), "buzz"), connect.NewRequest(&typesv1.LabelNamesRequest{}))
   107  	require.NoError(t, err)
   108  	require.Equal(t, []string{"__period_type__", "__period_unit__", "__profile_type__", "__type__", "__unit__", "buzz"}, labelNames.Msg.Names)
   109  
   110  	labelsValues, err := ing.LabelValues(tenant.InjectTenantID(context.Background(), "foo"), connect.NewRequest(&typesv1.LabelValuesRequest{Name: "foo"}))
   111  	require.NoError(t, err)
   112  	require.Equal(t, []string{"bar"}, labelsValues.Msg.Names)
   113  
   114  	labelsValues, err = ing.LabelValues(tenant.InjectTenantID(context.Background(), "buzz"), connect.NewRequest(&typesv1.LabelValuesRequest{Name: "buzz"}))
   115  	require.NoError(t, err)
   116  	require.Equal(t, []string{"bazz"}, labelsValues.Msg.Names)
   117  
   118  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing))
   119  }
   120  
   121  func Test_Query_TenantNotFound(t *testing.T) {
   122  	dbPath := t.TempDir()
   123  	logger := log.NewJSONLogger(os.Stdout)
   124  	reg := prometheus.NewRegistry()
   125  	ctx := phlarecontext.WithLogger(context.Background(), logger)
   126  	ctx = phlarecontext.WithRegistry(ctx, reg)
   127  	cfg := client.Config{
   128  		StorageBackendConfig: client.StorageBackendConfig{
   129  			Backend: client.Filesystem,
   130  			Filesystem: filesystem.Config{
   131  				Directory: dbPath,
   132  			},
   133  		},
   134  	}
   135  
   136  	// set the localPath
   137  	localPath := t.TempDir()
   138  
   139  	// foo has an empty local dir
   140  	fooLocalPath := filepath.Join(localPath, "foo", "local")
   141  	require.NoError(t, os.MkdirAll(fooLocalPath, 0o755))
   142  	require.NoError(t, os.WriteFile(filepath.Join(fooLocalPath, "shipper.json"), []byte(`{"version":1,"uploaded":null}`), 0o755))
   143  
   144  	fs, err := client.NewBucket(ctx, cfg, "storage")
   145  	require.NoError(t, err)
   146  
   147  	ing, err := New(ctx, defaultIngesterTestConfig(t), phlaredb.Config{
   148  		DataPath:         localPath,
   149  		MaxBlockDuration: 30 * time.Hour,
   150  	}, fs, &fakeLimits{}, 0)
   151  	require.NoError(t, err)
   152  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing))
   153  
   154  	labelsValues, err := ing.LabelValues(tenant.InjectTenantID(context.Background(), "foo"), connect.NewRequest(&typesv1.LabelValuesRequest{Name: "foo"}))
   155  	require.NoError(t, err)
   156  	require.Empty(t, labelsValues.Msg.Names)
   157  
   158  	labelsNames, err := ing.LabelNames(tenant.InjectTenantID(context.Background(), "buzz"), connect.NewRequest(&typesv1.LabelNamesRequest{}))
   159  	require.NoError(t, err)
   160  	require.Empty(t, labelsNames.Msg.Names)
   161  
   162  	// check that no tenant are initialized
   163  	ing.instancesMtx.RLock()
   164  	require.Len(t, ing.instances, 0)
   165  	ing.instancesMtx.RUnlock()
   166  
   167  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing))
   168  }
   169  
   170  func Test_Query_TenantFound(t *testing.T) {
   171  	dbPath := t.TempDir()
   172  	logger := log.NewJSONLogger(os.Stdout)
   173  	phlareCtx := phlarecontext.WithLogger(context.Background(), logger)
   174  
   175  	cfg := client.Config{
   176  		StorageBackendConfig: client.StorageBackendConfig{
   177  			Backend: client.Filesystem,
   178  			Filesystem: filesystem.Config{
   179  				Directory: dbPath,
   180  			},
   181  		},
   182  	}
   183  
   184  	fs, err := client.NewBucket(phlareCtx, cfg, "storage")
   185  	require.NoError(t, err)
   186  
   187  	ing, err := New(phlareCtx, defaultIngesterTestConfig(t), phlaredb.Config{
   188  		DataPath:         dbPath,
   189  		MaxBlockDuration: 30 * time.Hour,
   190  	}, fs, &fakeLimits{}, 0)
   191  	require.NoError(t, err)
   192  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing))
   193  
   194  	req := &connect.Request[pushv1.PushRequest]{
   195  		Msg: &pushv1.PushRequest{
   196  			Series: []*pushv1.RawProfileSeries{
   197  				{
   198  					Labels: phlaremodel.LabelsFromStrings("foo", "bar"),
   199  					Samples: []*pushv1.RawSample{
   200  						{
   201  							ID:         uuid.NewString(),
   202  							RawProfile: testProfile(t),
   203  						},
   204  					},
   205  				},
   206  			},
   207  		},
   208  	}
   209  
   210  	ctx := tenant.InjectTenantID(context.Background(), "foo")
   211  	_, err = ing.Push(ctx, req)
   212  	require.NoError(t, err)
   213  
   214  	query := &typesv1.LabelValuesRequest{
   215  		Name:  "foo",
   216  		Start: time.Now().Add(-1 * time.Hour).UnixMilli(),
   217  		End:   time.Now().Add(time.Hour).UnixMilli(),
   218  	}
   219  
   220  	labelsValues, err := ing.LabelValues(ctx, connect.NewRequest(query))
   221  	require.NoError(t, err)
   222  	require.Equal(t, []string{"bar"}, labelsValues.Msg.Names)
   223  
   224  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing))
   225  
   226  	// Open the ingester again and check if the data is
   227  	// available for queries before the first push request.
   228  
   229  	ing, err = New(phlareCtx, defaultIngesterTestConfig(t), phlaredb.Config{
   230  		DataPath:         dbPath,
   231  		MaxBlockDuration: 30 * time.Hour,
   232  	}, fs, &fakeLimits{}, 0)
   233  	require.NoError(t, err)
   234  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing))
   235  
   236  	labelsValues, err = ing.LabelValues(ctx, connect.NewRequest(query))
   237  	require.NoError(t, err)
   238  	require.Equal(t, []string{"bar"}, labelsValues.Msg.Names)
   239  
   240  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing))
   241  }