github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/block/fetcher_test.go (about) 1 // SPDX-License-Identifier: AGPL-3.0-only 2 3 package block_test 4 5 import ( 6 "context" 7 "path" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "github.com/go-kit/log" 13 "github.com/oklog/ulid/v2" 14 "github.com/prometheus/client_golang/prometheus" 15 "github.com/prometheus/client_golang/prometheus/promauto" 16 "github.com/prometheus/client_golang/prometheus/testutil" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 20 objstore_testutil "github.com/grafana/pyroscope/pkg/objstore/testutil" 21 "github.com/grafana/pyroscope/pkg/phlaredb/block" 22 block_testutil "github.com/grafana/pyroscope/pkg/phlaredb/block/testutil" 23 "github.com/grafana/pyroscope/pkg/pprof/testhelper" 24 phlarecontext "github.com/grafana/pyroscope/pkg/pyroscope/context" 25 ) 26 27 func TestMetaFetcher_Fetch_ShouldReturnDiscoveredBlocksIncludingMarkedForDeletion(t *testing.T) { 28 var ( 29 ctx = context.Background() 30 reg = prometheus.NewPedanticRegistry() 31 logger = log.NewNopLogger() 32 ) 33 34 // Create a bucket client with global markers. 35 bkt, _ := objstore_testutil.NewFilesystemBucket(t, ctx, t.TempDir()) 36 bkt = block.BucketWithGlobalMarkers(bkt) 37 38 f, err := block.NewMetaFetcher(logger, 10, bkt, t.TempDir(), reg, nil) 39 require.NoError(t, err) 40 41 t.Run("should return no metas and no partials on no block in the storage", func(t *testing.T) { 42 actualMetas, actualPartials, actualErr := f.Fetch(ctx) 43 require.NoError(t, actualErr) 44 require.Empty(t, actualMetas) 45 require.Empty(t, actualPartials) 46 47 assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 48 # HELP blocks_meta_sync_failures_total Total blocks metadata synchronization failures 49 # TYPE blocks_meta_sync_failures_total counter 50 blocks_meta_sync_failures_total 0 51 52 # HELP blocks_meta_synced Number of block metadata synced 53 # TYPE blocks_meta_synced gauge 54 blocks_meta_synced{state="corrupted-meta-json"} 0 55 blocks_meta_synced{state="duplicate"} 0 56 blocks_meta_synced{state="failed"} 0 57 blocks_meta_synced{state="label-excluded"} 0 58 blocks_meta_synced{state="loaded"} 0 59 blocks_meta_synced{state="marked-for-deletion"} 0 60 blocks_meta_synced{state="marked-for-no-compact"} 0 61 blocks_meta_synced{state="no-meta-json"} 0 62 blocks_meta_synced{state="time-excluded"} 0 63 64 # HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts 65 # TYPE blocks_meta_syncs_total counter 66 blocks_meta_syncs_total 1 67 `), "blocks_meta_syncs_total", "blocks_meta_sync_failures_total", "blocks_meta_synced")) 68 }) 69 70 // Upload a block. 71 block1ID, block1Dir := createTestBlock(t) 72 require.NoError(t, block.Upload(ctx, logger, bkt, block1Dir)) 73 74 // Upload a partial block. 75 block2ID, block2Dir := createTestBlock(t) 76 require.NoError(t, block.Upload(ctx, logger, bkt, block2Dir)) 77 require.NoError(t, bkt.Delete(ctx, path.Join(block2ID.String(), block.MetaFilename))) 78 79 t.Run("should return metas and partials on some blocks in the storage", func(t *testing.T) { 80 actualMetas, actualPartials, actualErr := f.Fetch(ctx) 81 require.NoError(t, actualErr) 82 require.Len(t, actualMetas, 1) 83 require.Contains(t, actualMetas, block1ID) 84 require.Len(t, actualPartials, 1) 85 require.Contains(t, actualPartials, block2ID) 86 87 assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 88 # HELP blocks_meta_sync_failures_total Total blocks metadata synchronization failures 89 # TYPE blocks_meta_sync_failures_total counter 90 blocks_meta_sync_failures_total 0 91 92 # HELP blocks_meta_synced Number of block metadata synced 93 # TYPE blocks_meta_synced gauge 94 blocks_meta_synced{state="corrupted-meta-json"} 0 95 blocks_meta_synced{state="duplicate"} 0 96 blocks_meta_synced{state="failed"} 0 97 blocks_meta_synced{state="label-excluded"} 0 98 blocks_meta_synced{state="loaded"} 1 99 blocks_meta_synced{state="marked-for-deletion"} 0 100 blocks_meta_synced{state="marked-for-no-compact"} 0 101 blocks_meta_synced{state="no-meta-json"} 1 102 blocks_meta_synced{state="time-excluded"} 0 103 104 # HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts 105 # TYPE blocks_meta_syncs_total counter 106 blocks_meta_syncs_total 2 107 `), "blocks_meta_syncs_total", "blocks_meta_sync_failures_total", "blocks_meta_synced")) 108 }) 109 110 // Upload a block and mark it for deletion. 111 block3ID, block3Dir := createTestBlock(t) 112 require.NoError(t, block.Upload(ctx, logger, bkt, block3Dir)) 113 require.NoError(t, block.MarkForDeletion(ctx, logger, bkt, block3ID, "", false, promauto.With(nil).NewCounter(prometheus.CounterOpts{}))) 114 115 t.Run("should include blocks marked for deletion", func(t *testing.T) { 116 actualMetas, actualPartials, actualErr := f.Fetch(ctx) 117 require.NoError(t, actualErr) 118 require.Len(t, actualMetas, 2) 119 require.Contains(t, actualMetas, block1ID) 120 require.Contains(t, actualMetas, block3ID) 121 require.Len(t, actualPartials, 1) 122 require.Contains(t, actualPartials, block2ID) 123 124 assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 125 # HELP blocks_meta_sync_failures_total Total blocks metadata synchronization failures 126 # TYPE blocks_meta_sync_failures_total counter 127 blocks_meta_sync_failures_total 0 128 129 # HELP blocks_meta_synced Number of block metadata synced 130 # TYPE blocks_meta_synced gauge 131 blocks_meta_synced{state="corrupted-meta-json"} 0 132 blocks_meta_synced{state="duplicate"} 0 133 blocks_meta_synced{state="failed"} 0 134 blocks_meta_synced{state="label-excluded"} 0 135 blocks_meta_synced{state="loaded"} 2 136 blocks_meta_synced{state="marked-for-deletion"} 0 137 blocks_meta_synced{state="marked-for-no-compact"} 0 138 blocks_meta_synced{state="no-meta-json"} 1 139 blocks_meta_synced{state="time-excluded"} 0 140 141 # HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts 142 # TYPE blocks_meta_syncs_total counter 143 blocks_meta_syncs_total 3 144 `), "blocks_meta_syncs_total", "blocks_meta_sync_failures_total", "blocks_meta_synced")) 145 }) 146 } 147 148 func TestMetaFetcher_FetchWithoutMarkedForDeletion_ShouldReturnDiscoveredBlocksExcludingMarkedForDeletion(t *testing.T) { 149 var ( 150 ctx = context.Background() 151 reg = prometheus.NewPedanticRegistry() 152 logger = log.NewNopLogger() 153 ) 154 155 // Create a bucket client with global markers. 156 bkt, _ := objstore_testutil.NewFilesystemBucket(t, ctx, t.TempDir()) 157 bkt = block.BucketWithGlobalMarkers(bkt) 158 159 f, err := block.NewMetaFetcher(logger, 10, bkt, t.TempDir(), reg, nil) 160 require.NoError(t, err) 161 162 t.Run("should return no metas and no partials on no block in the storage", func(t *testing.T) { 163 actualMetas, actualPartials, actualErr := f.FetchWithoutMarkedForDeletion(ctx) 164 require.NoError(t, actualErr) 165 require.Empty(t, actualMetas) 166 require.Empty(t, actualPartials) 167 168 assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 169 # HELP blocks_meta_sync_failures_total Total blocks metadata synchronization failures 170 # TYPE blocks_meta_sync_failures_total counter 171 blocks_meta_sync_failures_total 0 172 173 # HELP blocks_meta_synced Number of block metadata synced 174 # TYPE blocks_meta_synced gauge 175 blocks_meta_synced{state="corrupted-meta-json"} 0 176 blocks_meta_synced{state="duplicate"} 0 177 blocks_meta_synced{state="failed"} 0 178 blocks_meta_synced{state="label-excluded"} 0 179 blocks_meta_synced{state="loaded"} 0 180 blocks_meta_synced{state="marked-for-deletion"} 0 181 blocks_meta_synced{state="marked-for-no-compact"} 0 182 blocks_meta_synced{state="no-meta-json"} 0 183 blocks_meta_synced{state="time-excluded"} 0 184 185 # HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts 186 # TYPE blocks_meta_syncs_total counter 187 blocks_meta_syncs_total 1 188 `), "blocks_meta_syncs_total", "blocks_meta_sync_failures_total", "blocks_meta_synced")) 189 }) 190 191 // Upload a block. 192 block1ID, block1Dir := createTestBlock(t) 193 require.NoError(t, block.Upload(ctx, logger, bkt, block1Dir)) 194 195 // Upload a partial block. 196 block2ID, block2Dir := createTestBlock(t) 197 require.NoError(t, block.Upload(ctx, logger, bkt, block2Dir)) 198 require.NoError(t, bkt.Delete(ctx, path.Join(block2ID.String(), block.MetaFilename))) 199 200 t.Run("should return metas and partials on some blocks in the storage", func(t *testing.T) { 201 actualMetas, actualPartials, actualErr := f.FetchWithoutMarkedForDeletion(ctx) 202 require.NoError(t, actualErr) 203 require.Len(t, actualMetas, 1) 204 require.Contains(t, actualMetas, block1ID) 205 require.Len(t, actualPartials, 1) 206 require.Contains(t, actualPartials, block2ID) 207 208 assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 209 # HELP blocks_meta_sync_failures_total Total blocks metadata synchronization failures 210 # TYPE blocks_meta_sync_failures_total counter 211 blocks_meta_sync_failures_total 0 212 213 # HELP blocks_meta_synced Number of block metadata synced 214 # TYPE blocks_meta_synced gauge 215 blocks_meta_synced{state="corrupted-meta-json"} 0 216 blocks_meta_synced{state="duplicate"} 0 217 blocks_meta_synced{state="failed"} 0 218 blocks_meta_synced{state="label-excluded"} 0 219 blocks_meta_synced{state="loaded"} 1 220 blocks_meta_synced{state="marked-for-deletion"} 0 221 blocks_meta_synced{state="marked-for-no-compact"} 0 222 blocks_meta_synced{state="no-meta-json"} 1 223 blocks_meta_synced{state="time-excluded"} 0 224 225 # HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts 226 # TYPE blocks_meta_syncs_total counter 227 blocks_meta_syncs_total 2 228 `), "blocks_meta_syncs_total", "blocks_meta_sync_failures_total", "blocks_meta_synced")) 229 }) 230 231 // Upload a block and mark it for deletion. 232 block3ID, block3Dir := createTestBlock(t) 233 require.NoError(t, block.Upload(ctx, logger, bkt, block3Dir)) 234 require.NoError(t, block.MarkForDeletion(ctx, logger, bkt, block3ID, "", false, promauto.With(nil).NewCounter(prometheus.CounterOpts{}))) 235 236 t.Run("should include blocks marked for deletion", func(t *testing.T) { 237 actualMetas, actualPartials, actualErr := f.FetchWithoutMarkedForDeletion(ctx) 238 require.NoError(t, actualErr) 239 require.Len(t, actualMetas, 1) 240 require.Contains(t, actualMetas, block1ID) 241 require.Len(t, actualPartials, 1) 242 require.Contains(t, actualPartials, block2ID) 243 244 assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 245 # HELP blocks_meta_sync_failures_total Total blocks metadata synchronization failures 246 # TYPE blocks_meta_sync_failures_total counter 247 blocks_meta_sync_failures_total 0 248 249 # HELP blocks_meta_synced Number of block metadata synced 250 # TYPE blocks_meta_synced gauge 251 blocks_meta_synced{state="corrupted-meta-json"} 0 252 blocks_meta_synced{state="duplicate"} 0 253 blocks_meta_synced{state="failed"} 0 254 blocks_meta_synced{state="label-excluded"} 0 255 blocks_meta_synced{state="loaded"} 1 256 blocks_meta_synced{state="marked-for-deletion"} 1 257 blocks_meta_synced{state="marked-for-no-compact"} 0 258 blocks_meta_synced{state="no-meta-json"} 1 259 blocks_meta_synced{state="time-excluded"} 0 260 261 # HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts 262 # TYPE blocks_meta_syncs_total counter 263 blocks_meta_syncs_total 3 264 `), "blocks_meta_syncs_total", "blocks_meta_sync_failures_total", "blocks_meta_synced")) 265 }) 266 } 267 268 func TestMetaFetcher_ShouldNotIssueAnyAPICallToObjectStorageIfAllBlockMetasAreCached(t *testing.T) { 269 var ( 270 ctx = context.Background() 271 logger = log.NewNopLogger() 272 fetcherDir = t.TempDir() 273 ) 274 bkt, _ := objstore_testutil.NewFilesystemBucket(t, ctx, fetcherDir) 275 276 // Upload few blocks. 277 block1ID, block1Dir := createTestBlock(t) 278 require.NoError(t, block.Upload(ctx, logger, bkt, block1Dir)) 279 block2ID, block2Dir := createTestBlock(t) 280 require.NoError(t, block.Upload(ctx, logger, bkt, block2Dir)) 281 282 // Create a fetcher and fetch block metas to populate the cache on disk. 283 reg1 := prometheus.NewPedanticRegistry() 284 ctx = phlarecontext.WithRegistry(ctx, reg1) 285 bkt, _ = objstore_testutil.NewFilesystemBucket(t, ctx, fetcherDir) 286 fetcher1, err := block.NewMetaFetcher(logger, 10, bkt, fetcherDir, nil, nil) 287 require.NoError(t, err) 288 actualMetas, _, actualErr := fetcher1.Fetch(ctx) 289 require.NoError(t, actualErr) 290 require.Len(t, actualMetas, 2) 291 require.Contains(t, actualMetas, block1ID) 292 require.Contains(t, actualMetas, block2ID) 293 294 assert.NoError(t, testutil.GatherAndCompare(reg1, strings.NewReader(` 295 # HELP objstore_bucket_operations_total Total number of all attempted operations against a bucket. 296 # TYPE objstore_bucket_operations_total counter 297 objstore_bucket_operations_total{bucket="test",operation="attributes"} 0 298 objstore_bucket_operations_total{bucket="test",operation="exists"} 0 299 objstore_bucket_operations_total{bucket="test",operation="delete"} 0 300 objstore_bucket_operations_total{bucket="test",operation="upload"} 0 301 objstore_bucket_operations_total{bucket="test",operation="get"} 2 302 objstore_bucket_operations_total{bucket="test",operation="get_range"} 0 303 objstore_bucket_operations_total{bucket="test",operation="iter"} 1 304 `), "objstore_bucket_operations_total")) 305 306 // Create a new fetcher and fetch blocks again. This time we expect all meta.json to be loaded from cache. 307 reg2 := prometheus.NewPedanticRegistry() 308 ctx = phlarecontext.WithRegistry(ctx, reg2) 309 bkt, _ = objstore_testutil.NewFilesystemBucket(t, ctx, fetcherDir) 310 fetcher2, err := block.NewMetaFetcher(logger, 10, bkt, fetcherDir, nil, nil) 311 require.NoError(t, err) 312 actualMetas, _, actualErr = fetcher2.Fetch(ctx) 313 require.NoError(t, actualErr) 314 require.Len(t, actualMetas, 2) 315 require.Contains(t, actualMetas, block1ID) 316 require.Contains(t, actualMetas, block2ID) 317 318 assert.NoError(t, testutil.GatherAndCompare(reg2, strings.NewReader(` 319 # HELP objstore_bucket_operations_total Total number of all attempted operations against a bucket. 320 # TYPE objstore_bucket_operations_total counter 321 objstore_bucket_operations_total{bucket="test",operation="attributes"} 0 322 objstore_bucket_operations_total{bucket="test",operation="delete"} 0 323 objstore_bucket_operations_total{bucket="test",operation="exists"} 0 324 objstore_bucket_operations_total{bucket="test",operation="get"} 0 325 objstore_bucket_operations_total{bucket="test",operation="get_range"} 0 326 objstore_bucket_operations_total{bucket="test",operation="iter"} 1 327 objstore_bucket_operations_total{bucket="test",operation="upload"} 0 328 `), "objstore_bucket_operations_total")) 329 } 330 331 func createTestBlock(t *testing.T) (blockID ulid.ULID, blockDir string) { 332 meta, dir := block_testutil.CreateBlock(t, func() []*testhelper.ProfileBuilder { 333 return []*testhelper.ProfileBuilder{ 334 testhelper.NewProfileBuilder(int64(1)). 335 CPUProfile(). 336 WithLabels( 337 "job", "a", 338 ).ForStacktraceString("foo", "bar", "baz").AddSamples(1), 339 } 340 }) 341 blockID = meta.ULID 342 blockDir = filepath.Join(dir, blockID.String()) 343 return 344 }