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 }