github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/block_querier_test.go (about)

     1  package phlaredb
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"math/rand"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"connectrpc.com/connect"
    14  	"github.com/oklog/ulid/v2"
    15  	"github.com/prometheus/common/model"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  	"go.uber.org/goleak"
    19  	"golang.org/x/sync/errgroup"
    20  
    21  	ingesterv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1"
    22  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    23  	"github.com/grafana/pyroscope/pkg/iter"
    24  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    25  	"github.com/grafana/pyroscope/pkg/objstore/providers/filesystem"
    26  	"github.com/grafana/pyroscope/pkg/phlaredb/block"
    27  	"github.com/grafana/pyroscope/pkg/phlaredb/tsdb/index"
    28  	"github.com/grafana/pyroscope/pkg/pprof/testhelper"
    29  )
    30  
    31  const testDataPath = "./block/testdata/"
    32  
    33  func TestQuerierBlockEviction(t *testing.T) {
    34  	type testCase struct {
    35  		blocks     []string
    36  		expected   []string
    37  		notEvicted bool
    38  	}
    39  
    40  	blockToEvict := "01H002D4Z9PKWSS17Q3XY1VEM9"
    41  	testCases := []testCase{
    42  		{
    43  			notEvicted: true,
    44  		},
    45  		{
    46  			blocks:     []string{"01H002D4Z9ES0DHMMSD18H5J5M"},
    47  			expected:   []string{"01H002D4Z9ES0DHMMSD18H5J5M"},
    48  			notEvicted: true,
    49  		},
    50  		{
    51  			blocks:   []string{blockToEvict},
    52  			expected: []string{},
    53  		},
    54  		{
    55  			blocks:   []string{blockToEvict, "01H002D4Z9ES0DHMMSD18H5J5M"},
    56  			expected: []string{"01H002D4Z9ES0DHMMSD18H5J5M"},
    57  		},
    58  		{
    59  			blocks:   []string{"01H002D4Z9ES0DHMMSD18H5J5M", blockToEvict},
    60  			expected: []string{"01H002D4Z9ES0DHMMSD18H5J5M"},
    61  		},
    62  		{
    63  			blocks:   []string{"01H002D4Z9ES0DHMMSD18H5J5M", blockToEvict, "01H003A2QTY5JF30Z441CDQE70"},
    64  			expected: []string{"01H002D4Z9ES0DHMMSD18H5J5M", "01H003A2QTY5JF30Z441CDQE70"},
    65  		},
    66  		{
    67  			blocks:   []string{"01H003A2QTY5JF30Z441CDQE70", blockToEvict, "01H002D4Z9ES0DHMMSD18H5J5M"},
    68  			expected: []string{"01H003A2QTY5JF30Z441CDQE70", "01H002D4Z9ES0DHMMSD18H5J5M"},
    69  		},
    70  	}
    71  
    72  	for _, tc := range testCases {
    73  		q := BlockQuerier{queriers: make([]*singleBlockQuerier, len(tc.blocks))}
    74  		for i, b := range tc.blocks {
    75  			q.queriers[i] = &singleBlockQuerier{
    76  				meta:    &block.Meta{ULID: ulid.MustParse(b)},
    77  				metrics: NewBlocksMetrics(nil),
    78  			}
    79  		}
    80  
    81  		evicted, err := q.evict(ulid.MustParse(blockToEvict))
    82  		require.NoError(t, err)
    83  		require.Equal(t, !tc.notEvicted, evicted)
    84  
    85  		actual := make([]string, 0, len(tc.expected))
    86  		for _, b := range q.queriers {
    87  			actual = append(actual, b.meta.ULID.String())
    88  		}
    89  
    90  		require.ElementsMatch(t, tc.expected, actual)
    91  	}
    92  }
    93  
    94  type profileCounter struct {
    95  	iter.Iterator[Profile]
    96  	count int
    97  }
    98  
    99  func (p *profileCounter) Next() bool {
   100  	r := p.Iterator.Next()
   101  	if r {
   102  		p.count++
   103  	}
   104  
   105  	return r
   106  }
   107  
   108  func TestBlockCompatability(t *testing.T) {
   109  	path := testDataPath
   110  	bucket, err := filesystem.NewBucket(path)
   111  	require.NoError(t, err)
   112  
   113  	ctx := context.Background()
   114  	metas, err := NewBlockQuerier(ctx, bucket).BlockMetas(ctx)
   115  	require.NoError(t, err)
   116  
   117  	for _, meta := range metas {
   118  		t.Run(fmt.Sprintf("block-v%d-%s", meta.Version, meta.ULID.String()), func(t *testing.T) {
   119  			q := NewSingleBlockQuerierFromMeta(ctx, bucket, meta)
   120  			require.NoError(t, q.Open(ctx))
   121  
   122  			profilesTypes, err := q.index.LabelValues("__profile_type__")
   123  			require.NoError(t, err)
   124  
   125  			profileCount := 0
   126  
   127  			for _, profileType := range profilesTypes {
   128  				t.Log(profileType)
   129  				profileTypeParts := strings.Split(profileType, ":")
   130  
   131  				it, err := q.SelectMatchingProfiles(ctx, &ingesterv1.SelectProfilesRequest{
   132  					LabelSelector: "{}",
   133  					Start:         0,
   134  					End:           time.Now().UnixMilli(),
   135  					Type: &typesv1.ProfileType{
   136  						Name:       profileTypeParts[0],
   137  						SampleType: profileTypeParts[1],
   138  						SampleUnit: profileTypeParts[2],
   139  						PeriodType: profileTypeParts[3],
   140  						PeriodUnit: profileTypeParts[4],
   141  					},
   142  				})
   143  				require.NoError(t, err)
   144  
   145  				pcIt := &profileCounter{Iterator: it}
   146  
   147  				// TODO: It would be nice actually comparing the whole profile, but at present the result is not deterministic.
   148  				_, err = q.MergePprof(ctx, pcIt, 0, nil)
   149  				require.NoError(t, err)
   150  
   151  				profileCount += pcIt.count
   152  			}
   153  
   154  			require.Equal(t, int(meta.Stats.NumProfiles), profileCount)
   155  		})
   156  	}
   157  }
   158  
   159  func TestBlockCompatability_SelectMergeSpans(t *testing.T) {
   160  	path := testDataPath
   161  	bucket, err := filesystem.NewBucket(path)
   162  	require.NoError(t, err)
   163  
   164  	ctx := context.Background()
   165  	metas, err := NewBlockQuerier(ctx, bucket).BlockMetas(ctx)
   166  	require.NoError(t, err)
   167  
   168  	for _, meta := range metas {
   169  		t.Run(fmt.Sprintf("block-v%d-%s", meta.Version, meta.ULID.String()), func(t *testing.T) {
   170  			q := NewSingleBlockQuerierFromMeta(ctx, bucket, meta)
   171  			require.NoError(t, q.Open(ctx))
   172  
   173  			profilesTypes, err := q.index.LabelValues("__profile_type__")
   174  			require.NoError(t, err)
   175  
   176  			profileCount := 0
   177  
   178  			for _, profileType := range profilesTypes {
   179  				t.Log(profileType)
   180  				profileTypeParts := strings.Split(profileType, ":")
   181  
   182  				it, err := q.SelectMatchingProfiles(ctx, &ingesterv1.SelectProfilesRequest{
   183  					LabelSelector: "{}",
   184  					Start:         0,
   185  					End:           time.Now().UnixMilli(),
   186  					Type: &typesv1.ProfileType{
   187  						Name:       profileTypeParts[0],
   188  						SampleType: profileTypeParts[1],
   189  						SampleUnit: profileTypeParts[2],
   190  						PeriodType: profileTypeParts[3],
   191  						PeriodUnit: profileTypeParts[4],
   192  					},
   193  				})
   194  				require.NoError(t, err)
   195  
   196  				pcIt := &profileCounter{Iterator: it}
   197  
   198  				spanSelector, err := phlaremodel.NewSpanSelector([]string{})
   199  				require.NoError(t, err)
   200  				resp, err := q.MergeBySpans(ctx, pcIt, spanSelector)
   201  				require.NoError(t, err)
   202  
   203  				require.Zero(t, resp.Total())
   204  				profileCount += pcIt.count
   205  			}
   206  
   207  			require.Zero(t, profileCount)
   208  		})
   209  	}
   210  }
   211  
   212  type fakeQuerier struct {
   213  	Querier
   214  	doErr bool
   215  }
   216  
   217  func (f *fakeQuerier) BlockID() string {
   218  	return "block-id"
   219  }
   220  
   221  func (f *fakeQuerier) SelectMatchingProfiles(ctx context.Context, params *ingesterv1.SelectProfilesRequest) (iter.Iterator[Profile], error) {
   222  	// add some jitter
   223  	time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
   224  	if f.doErr {
   225  		return nil, fmt.Errorf("fake error")
   226  	}
   227  	profiles := []Profile{}
   228  	for i := 0; i < 100000; i++ {
   229  		profiles = append(profiles, BlockProfile{})
   230  	}
   231  	return iter.NewSliceIterator(profiles), nil
   232  }
   233  
   234  func openSingleBlockQuerierIndex(t *testing.T, blockID string) *singleBlockQuerier {
   235  	t.Helper()
   236  
   237  	reader, err := index.NewFileReader(fmt.Sprintf("testdata/%s/index.tsdb", blockID))
   238  	require.NoError(t, err)
   239  
   240  	q := &singleBlockQuerier{
   241  		metrics: NewBlocksMetrics(nil),
   242  		meta:    &block.Meta{ULID: ulid.MustParse(blockID)},
   243  		opened:  true, // Skip trying to open the block.
   244  		index:   reader,
   245  	}
   246  	return q
   247  }
   248  
   249  func TestSelectMatchingProfilesCleanUp(t *testing.T) {
   250  	defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
   251  
   252  	_, err := SelectMatchingProfiles(context.Background(), &ingesterv1.SelectProfilesRequest{}, Queriers{
   253  		&fakeQuerier{},
   254  		&fakeQuerier{},
   255  		&fakeQuerier{},
   256  		&fakeQuerier{},
   257  		&fakeQuerier{doErr: true},
   258  	})
   259  	require.Error(t, err)
   260  }
   261  
   262  func Test_singleBlockQuerier_Series(t *testing.T) {
   263  	ctx := context.Background()
   264  	q := openSingleBlockQuerierIndex(t, "01HA2V3CPSZ9E0HMQNNHH89WSS")
   265  
   266  	t.Run("get all names", func(t *testing.T) {
   267  		want := []string{
   268  			"__delta__",
   269  			"__name__",
   270  			"__period_type__",
   271  			"__period_unit__",
   272  			"__profile_type__",
   273  			"__service_name__",
   274  			"__type__",
   275  			"__unit__",
   276  			"foo",
   277  			"function",
   278  			"pyroscope_spy",
   279  			"service_name",
   280  			"target",
   281  			"version",
   282  		}
   283  		got, err := q.index.LabelNames()
   284  		assert.NoError(t, err)
   285  		assert.Equal(t, want, got)
   286  	})
   287  
   288  	t.Run("get label", func(t *testing.T) {
   289  		want := []*typesv1.Labels{
   290  			{Labels: []*typesv1.LabelPair{
   291  				{Name: "__name__", Value: "block"},
   292  			}},
   293  			{Labels: []*typesv1.LabelPair{
   294  				{Name: "__name__", Value: "goroutine"},
   295  			}},
   296  			{Labels: []*typesv1.LabelPair{
   297  				{Name: "__name__", Value: "memory"},
   298  			}},
   299  			{Labels: []*typesv1.LabelPair{
   300  				{Name: "__name__", Value: "mutex"},
   301  			}},
   302  			{Labels: []*typesv1.LabelPair{
   303  				{Name: "__name__", Value: "process_cpu"},
   304  			}},
   305  		}
   306  		got, err := q.Series(ctx, &ingesterv1.SeriesRequest{
   307  			LabelNames: []string{
   308  				"__name__",
   309  			},
   310  		})
   311  
   312  		assert.NoError(t, err)
   313  		assert.Equal(t, want, got)
   314  	})
   315  
   316  	t.Run("get label with matcher", func(t *testing.T) {
   317  		want := []*typesv1.Labels{
   318  			{Labels: []*typesv1.LabelPair{
   319  				{Name: "__name__", Value: "block"},
   320  			}},
   321  		}
   322  		got, err := q.Series(ctx, &ingesterv1.SeriesRequest{
   323  			Matchers:   []string{`{__name__="block"}`},
   324  			LabelNames: []string{"__name__"},
   325  		})
   326  
   327  		assert.NoError(t, err)
   328  		assert.Equal(t, want, got)
   329  	})
   330  
   331  	t.Run("get multiple labels", func(t *testing.T) {
   332  		want := []*typesv1.Labels{
   333  			{Labels: []*typesv1.LabelPair{
   334  				{Name: "__name__", Value: "block"},
   335  				{Name: "__type__", Value: "contentions"},
   336  			}},
   337  			{Labels: []*typesv1.LabelPair{
   338  				{Name: "__name__", Value: "block"},
   339  				{Name: "__type__", Value: "delay"},
   340  			}},
   341  			{Labels: []*typesv1.LabelPair{
   342  				{Name: "__name__", Value: "goroutine"},
   343  				{Name: "__type__", Value: "goroutines"},
   344  			}},
   345  			{Labels: []*typesv1.LabelPair{
   346  				{Name: "__name__", Value: "memory"},
   347  				{Name: "__type__", Value: "alloc_objects"},
   348  			}},
   349  			{Labels: []*typesv1.LabelPair{
   350  				{Name: "__name__", Value: "memory"},
   351  				{Name: "__type__", Value: "alloc_space"},
   352  			}},
   353  			{Labels: []*typesv1.LabelPair{
   354  				{Name: "__name__", Value: "memory"},
   355  				{Name: "__type__", Value: "inuse_objects"},
   356  			}},
   357  			{Labels: []*typesv1.LabelPair{
   358  				{Name: "__name__", Value: "memory"},
   359  				{Name: "__type__", Value: "inuse_space"},
   360  			}},
   361  			{Labels: []*typesv1.LabelPair{
   362  				{Name: "__name__", Value: "mutex"},
   363  				{Name: "__type__", Value: "contentions"},
   364  			}},
   365  			{Labels: []*typesv1.LabelPair{
   366  				{Name: "__name__", Value: "mutex"},
   367  				{Name: "__type__", Value: "delay"},
   368  			}},
   369  			{Labels: []*typesv1.LabelPair{
   370  				{Name: "__name__", Value: "process_cpu"},
   371  				{Name: "__type__", Value: "cpu"},
   372  			}},
   373  		}
   374  		got, err := q.Series(ctx, &ingesterv1.SeriesRequest{
   375  			LabelNames: []string{"__name__", "__type__"},
   376  		})
   377  
   378  		assert.NoError(t, err)
   379  		assert.Equal(t, want, got)
   380  	})
   381  
   382  	t.Run("get multiple labels with matcher", func(t *testing.T) {
   383  		want := []*typesv1.Labels{
   384  			{Labels: []*typesv1.LabelPair{
   385  				{Name: "__name__", Value: "memory"},
   386  				{Name: "__type__", Value: "alloc_objects"},
   387  			}},
   388  		}
   389  		got, err := q.Series(ctx, &ingesterv1.SeriesRequest{
   390  			Matchers:   []string{`{__name__="memory",__type__="alloc_objects"}`},
   391  			LabelNames: []string{"__name__", "__type__"},
   392  		})
   393  
   394  		assert.NoError(t, err)
   395  		assert.Equal(t, want, got)
   396  	})
   397  
   398  	t.Run("empty labels and empty matcher", func(t *testing.T) {
   399  		want := []*typesv1.Labels{
   400  			{Labels: []*typesv1.LabelPair{
   401  				{Name: "__delta__", Value: "false"},
   402  				{Name: "__name__", Value: "block"},
   403  				{Name: "__profile_type__", Value: "block:contentions:count::"},
   404  				{Name: "__service_name__", Value: "pyroscope"},
   405  				{Name: "__type__", Value: "contentions"},
   406  				{Name: "__unit__", Value: "count"},
   407  				{Name: "pyroscope_spy", Value: "gospy"},
   408  				{Name: "service_name", Value: "pyroscope"},
   409  				{Name: "target", Value: "all"},
   410  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   411  			}},
   412  			{Labels: []*typesv1.LabelPair{
   413  				{Name: "__delta__", Value: "false"},
   414  				{Name: "__name__", Value: "block"},
   415  				{Name: "__profile_type__", Value: "block:delay:nanoseconds::"},
   416  				{Name: "__service_name__", Value: "pyroscope"},
   417  				{Name: "__type__", Value: "delay"},
   418  				{Name: "__unit__", Value: "nanoseconds"},
   419  				{Name: "pyroscope_spy", Value: "gospy"},
   420  				{Name: "service_name", Value: "pyroscope"},
   421  				{Name: "target", Value: "all"},
   422  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   423  			}},
   424  			{Labels: []*typesv1.LabelPair{
   425  				{Name: "__delta__", Value: "false"},
   426  				{Name: "__name__", Value: "goroutine"},
   427  				{Name: "__profile_type__", Value: "goroutine:goroutines:count::"},
   428  				{Name: "__service_name__", Value: "pyroscope"},
   429  				{Name: "__type__", Value: "goroutines"},
   430  				{Name: "__unit__", Value: "count"},
   431  				{Name: "pyroscope_spy", Value: "gospy"},
   432  				{Name: "service_name", Value: "pyroscope"},
   433  				{Name: "target", Value: "all"},
   434  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   435  			}},
   436  			{Labels: []*typesv1.LabelPair{
   437  				{Name: "__delta__", Value: "false"},
   438  				{Name: "__name__", Value: "memory"},
   439  				{Name: "__profile_type__", Value: "memory:alloc_objects:count::"},
   440  				{Name: "__service_name__", Value: "pyroscope"},
   441  				{Name: "__type__", Value: "alloc_objects"},
   442  				{Name: "__unit__", Value: "count"},
   443  				{Name: "pyroscope_spy", Value: "gospy"},
   444  				{Name: "service_name", Value: "pyroscope"},
   445  				{Name: "target", Value: "all"},
   446  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   447  			}},
   448  			{Labels: []*typesv1.LabelPair{
   449  				{Name: "__delta__", Value: "false"},
   450  				{Name: "__name__", Value: "memory"},
   451  				{Name: "__profile_type__", Value: "memory:alloc_objects:count::"},
   452  				{Name: "__service_name__", Value: "simple.golang.app"},
   453  				{Name: "__type__", Value: "alloc_objects"},
   454  				{Name: "__unit__", Value: "count"},
   455  				{Name: "pyroscope_spy", Value: "gospy"},
   456  				{Name: "service_name", Value: "simple.golang.app"},
   457  			}},
   458  			{Labels: []*typesv1.LabelPair{
   459  				{Name: "__delta__", Value: "false"},
   460  				{Name: "__name__", Value: "memory"},
   461  				{Name: "__profile_type__", Value: "memory:alloc_space:bytes::"},
   462  				{Name: "__service_name__", Value: "pyroscope"},
   463  				{Name: "__type__", Value: "alloc_space"},
   464  				{Name: "__unit__", Value: "bytes"},
   465  				{Name: "pyroscope_spy", Value: "gospy"},
   466  				{Name: "service_name", Value: "pyroscope"},
   467  				{Name: "target", Value: "all"},
   468  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   469  			}},
   470  			{Labels: []*typesv1.LabelPair{
   471  				{Name: "__delta__", Value: "false"},
   472  				{Name: "__name__", Value: "memory"},
   473  				{Name: "__profile_type__", Value: "memory:alloc_space:bytes::"},
   474  				{Name: "__service_name__", Value: "simple.golang.app"},
   475  				{Name: "__type__", Value: "alloc_space"},
   476  				{Name: "__unit__", Value: "bytes"},
   477  				{Name: "pyroscope_spy", Value: "gospy"},
   478  				{Name: "service_name", Value: "simple.golang.app"},
   479  			}},
   480  			{Labels: []*typesv1.LabelPair{
   481  				{Name: "__delta__", Value: "false"},
   482  				{Name: "__name__", Value: "memory"},
   483  				{Name: "__profile_type__", Value: "memory:inuse_objects:count::"},
   484  				{Name: "__service_name__", Value: "pyroscope"},
   485  				{Name: "__type__", Value: "inuse_objects"},
   486  				{Name: "__unit__", Value: "count"},
   487  				{Name: "pyroscope_spy", Value: "gospy"},
   488  				{Name: "service_name", Value: "pyroscope"},
   489  				{Name: "target", Value: "all"},
   490  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   491  			}},
   492  			{Labels: []*typesv1.LabelPair{
   493  				{Name: "__delta__", Value: "false"},
   494  				{Name: "__name__", Value: "memory"},
   495  				{Name: "__profile_type__", Value: "memory:inuse_objects:count::"},
   496  				{Name: "__service_name__", Value: "simple.golang.app"},
   497  				{Name: "__type__", Value: "inuse_objects"},
   498  				{Name: "__unit__", Value: "count"},
   499  				{Name: "pyroscope_spy", Value: "gospy"},
   500  				{Name: "service_name", Value: "simple.golang.app"},
   501  			}},
   502  			{Labels: []*typesv1.LabelPair{
   503  				{Name: "__delta__", Value: "false"},
   504  				{Name: "__name__", Value: "memory"},
   505  				{Name: "__profile_type__", Value: "memory:inuse_space:bytes::"},
   506  				{Name: "__service_name__", Value: "pyroscope"},
   507  				{Name: "__type__", Value: "inuse_space"},
   508  				{Name: "__unit__", Value: "bytes"},
   509  				{Name: "pyroscope_spy", Value: "gospy"},
   510  				{Name: "service_name", Value: "pyroscope"},
   511  				{Name: "target", Value: "all"},
   512  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   513  			}},
   514  			{Labels: []*typesv1.LabelPair{
   515  				{Name: "__delta__", Value: "false"},
   516  				{Name: "__name__", Value: "memory"},
   517  				{Name: "__profile_type__", Value: "memory:inuse_space:bytes::"},
   518  				{Name: "__service_name__", Value: "simple.golang.app"},
   519  				{Name: "__type__", Value: "inuse_space"},
   520  				{Name: "__unit__", Value: "bytes"},
   521  				{Name: "pyroscope_spy", Value: "gospy"},
   522  				{Name: "service_name", Value: "simple.golang.app"},
   523  			}},
   524  			{Labels: []*typesv1.LabelPair{
   525  				{Name: "__delta__", Value: "false"},
   526  				{Name: "__name__", Value: "mutex"},
   527  				{Name: "__profile_type__", Value: "mutex:contentions:count::"},
   528  				{Name: "__service_name__", Value: "pyroscope"},
   529  				{Name: "__type__", Value: "contentions"},
   530  				{Name: "__unit__", Value: "count"},
   531  				{Name: "pyroscope_spy", Value: "gospy"},
   532  				{Name: "service_name", Value: "pyroscope"},
   533  				{Name: "target", Value: "all"},
   534  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   535  			}},
   536  			{Labels: []*typesv1.LabelPair{
   537  				{Name: "__delta__", Value: "false"},
   538  				{Name: "__name__", Value: "mutex"},
   539  				{Name: "__profile_type__", Value: "mutex:delay:nanoseconds::"},
   540  				{Name: "__service_name__", Value: "pyroscope"},
   541  				{Name: "__type__", Value: "delay"},
   542  				{Name: "__unit__", Value: "nanoseconds"},
   543  				{Name: "pyroscope_spy", Value: "gospy"},
   544  				{Name: "service_name", Value: "pyroscope"},
   545  				{Name: "target", Value: "all"},
   546  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   547  			}},
   548  			{Labels: []*typesv1.LabelPair{
   549  				{Name: "__delta__", Value: "false"},
   550  				{Name: "__name__", Value: "process_cpu"},
   551  				{Name: "__period_type__", Value: "cpu"},
   552  				{Name: "__period_unit__", Value: "nanoseconds"},
   553  				{Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"},
   554  				{Name: "__service_name__", Value: "pyroscope"},
   555  				{Name: "__type__", Value: "cpu"},
   556  				{Name: "__unit__", Value: "nanoseconds"},
   557  				{Name: "pyroscope_spy", Value: "gospy"},
   558  				{Name: "service_name", Value: "pyroscope"},
   559  				{Name: "target", Value: "all"},
   560  				{Name: "version", Value: "label-names-store-gateway-0e430f1e-WIP"},
   561  			}},
   562  			{Labels: []*typesv1.LabelPair{
   563  				{Name: "__delta__", Value: "false"},
   564  				{Name: "__name__", Value: "process_cpu"},
   565  				{Name: "__period_type__", Value: "cpu"},
   566  				{Name: "__period_unit__", Value: "nanoseconds"},
   567  				{Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"},
   568  				{Name: "__service_name__", Value: "simple.golang.app"},
   569  				{Name: "__type__", Value: "cpu"},
   570  				{Name: "__unit__", Value: "nanoseconds"},
   571  				{Name: "foo", Value: "bar"},
   572  				{Name: "function", Value: "fast"},
   573  				{Name: "pyroscope_spy", Value: "gospy"},
   574  				{Name: "service_name", Value: "simple.golang.app"},
   575  			}},
   576  			{Labels: []*typesv1.LabelPair{
   577  				{Name: "__delta__", Value: "false"},
   578  				{Name: "__name__", Value: "process_cpu"},
   579  				{Name: "__period_type__", Value: "cpu"},
   580  				{Name: "__period_unit__", Value: "nanoseconds"},
   581  				{Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"},
   582  				{Name: "__service_name__", Value: "simple.golang.app"},
   583  				{Name: "__type__", Value: "cpu"},
   584  				{Name: "__unit__", Value: "nanoseconds"},
   585  				{Name: "foo", Value: "bar"},
   586  				{Name: "function", Value: "slow"},
   587  				{Name: "pyroscope_spy", Value: "gospy"},
   588  				{Name: "service_name", Value: "simple.golang.app"},
   589  			}},
   590  			{Labels: []*typesv1.LabelPair{
   591  				{Name: "__delta__", Value: "false"},
   592  				{Name: "__name__", Value: "process_cpu"},
   593  				{Name: "__period_type__", Value: "cpu"},
   594  				{Name: "__period_unit__", Value: "nanoseconds"},
   595  				{Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"},
   596  				{Name: "__service_name__", Value: "simple.golang.app"},
   597  				{Name: "__type__", Value: "cpu"},
   598  				{Name: "__unit__", Value: "nanoseconds"},
   599  				{Name: "foo", Value: "bar"},
   600  				{Name: "pyroscope_spy", Value: "gospy"},
   601  				{Name: "service_name", Value: "simple.golang.app"},
   602  			}},
   603  			{Labels: []*typesv1.LabelPair{
   604  				{Name: "__delta__", Value: "false"},
   605  				{Name: "__name__", Value: "process_cpu"},
   606  				{Name: "__period_type__", Value: "cpu"},
   607  				{Name: "__period_unit__", Value: "nanoseconds"},
   608  				{Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"},
   609  				{Name: "__service_name__", Value: "simple.golang.app"},
   610  				{Name: "__type__", Value: "cpu"},
   611  				{Name: "__unit__", Value: "nanoseconds"},
   612  				{Name: "pyroscope_spy", Value: "gospy"},
   613  				{Name: "service_name", Value: "simple.golang.app"},
   614  			}},
   615  		}
   616  		got, err := q.Series(ctx, &ingesterv1.SeriesRequest{
   617  			Matchers:   []string{},
   618  			LabelNames: []string{},
   619  		})
   620  
   621  		assert.NoError(t, err)
   622  		assert.Equal(t, want, got)
   623  	})
   624  
   625  	t.Run("ui plugin", func(t *testing.T) {
   626  		want := []*typesv1.Labels{
   627  			{Labels: []*typesv1.LabelPair{
   628  				{Name: "__name__", Value: "block"},
   629  				{Name: "__profile_type__", Value: "block:contentions:count::"},
   630  				{Name: "__type__", Value: "contentions"},
   631  				{Name: "service_name", Value: "pyroscope"},
   632  			}},
   633  			{Labels: []*typesv1.LabelPair{
   634  				{Name: "__name__", Value: "block"},
   635  				{Name: "__profile_type__", Value: "block:delay:nanoseconds::"},
   636  				{Name: "__type__", Value: "delay"},
   637  				{Name: "service_name", Value: "pyroscope"},
   638  			}},
   639  			{Labels: []*typesv1.LabelPair{
   640  				{Name: "__name__", Value: "goroutine"},
   641  				{Name: "__profile_type__", Value: "goroutine:goroutines:count::"},
   642  				{Name: "__type__", Value: "goroutines"},
   643  				{Name: "service_name", Value: "pyroscope"},
   644  			}},
   645  			{Labels: []*typesv1.LabelPair{
   646  				{Name: "__name__", Value: "memory"},
   647  				{Name: "__profile_type__", Value: "memory:alloc_objects:count::"},
   648  				{Name: "__type__", Value: "alloc_objects"},
   649  				{Name: "service_name", Value: "pyroscope"},
   650  			}},
   651  			{Labels: []*typesv1.LabelPair{
   652  				{Name: "__name__", Value: "memory"},
   653  				{Name: "__profile_type__", Value: "memory:alloc_objects:count::"},
   654  				{Name: "__type__", Value: "alloc_objects"},
   655  				{Name: "service_name", Value: "simple.golang.app"},
   656  			}},
   657  			{Labels: []*typesv1.LabelPair{
   658  				{Name: "__name__", Value: "memory"},
   659  				{Name: "__profile_type__", Value: "memory:alloc_space:bytes::"},
   660  				{Name: "__type__", Value: "alloc_space"},
   661  				{Name: "service_name", Value: "pyroscope"},
   662  			}},
   663  			{Labels: []*typesv1.LabelPair{
   664  				{Name: "__name__", Value: "memory"},
   665  				{Name: "__profile_type__", Value: "memory:alloc_space:bytes::"},
   666  				{Name: "__type__", Value: "alloc_space"},
   667  				{Name: "service_name", Value: "simple.golang.app"},
   668  			}},
   669  			{Labels: []*typesv1.LabelPair{
   670  				{Name: "__name__", Value: "memory"},
   671  				{Name: "__profile_type__", Value: "memory:inuse_objects:count::"},
   672  				{Name: "__type__", Value: "inuse_objects"},
   673  				{Name: "service_name", Value: "pyroscope"},
   674  			}},
   675  			{Labels: []*typesv1.LabelPair{
   676  				{Name: "__name__", Value: "memory"},
   677  				{Name: "__profile_type__", Value: "memory:inuse_objects:count::"},
   678  				{Name: "__type__", Value: "inuse_objects"},
   679  				{Name: "service_name", Value: "simple.golang.app"},
   680  			}},
   681  			{Labels: []*typesv1.LabelPair{
   682  				{Name: "__name__", Value: "memory"},
   683  				{Name: "__profile_type__", Value: "memory:inuse_space:bytes::"},
   684  				{Name: "__type__", Value: "inuse_space"},
   685  				{Name: "service_name", Value: "pyroscope"},
   686  			}},
   687  			{Labels: []*typesv1.LabelPair{
   688  				{Name: "__name__", Value: "memory"},
   689  				{Name: "__profile_type__", Value: "memory:inuse_space:bytes::"},
   690  				{Name: "__type__", Value: "inuse_space"},
   691  				{Name: "service_name", Value: "simple.golang.app"},
   692  			}},
   693  			{Labels: []*typesv1.LabelPair{
   694  				{Name: "__name__", Value: "mutex"},
   695  				{Name: "__profile_type__", Value: "mutex:contentions:count::"},
   696  				{Name: "__type__", Value: "contentions"},
   697  				{Name: "service_name", Value: "pyroscope"},
   698  			}},
   699  			{Labels: []*typesv1.LabelPair{
   700  				{Name: "__name__", Value: "mutex"},
   701  				{Name: "__profile_type__", Value: "mutex:delay:nanoseconds::"},
   702  				{Name: "__type__", Value: "delay"},
   703  				{Name: "service_name", Value: "pyroscope"},
   704  			}},
   705  			{Labels: []*typesv1.LabelPair{
   706  				{Name: "__name__", Value: "process_cpu"},
   707  				{Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"},
   708  				{Name: "__type__", Value: "cpu"},
   709  				{Name: "service_name", Value: "pyroscope"},
   710  			}},
   711  			{Labels: []*typesv1.LabelPair{
   712  				{Name: "__name__", Value: "process_cpu"},
   713  				{Name: "__profile_type__", Value: "process_cpu:cpu:nanoseconds:cpu:nanoseconds"},
   714  				{Name: "__type__", Value: "cpu"},
   715  				{Name: "service_name", Value: "simple.golang.app"},
   716  			}},
   717  		}
   718  		got, err := q.Series(ctx, &ingesterv1.SeriesRequest{
   719  			Matchers: []string{},
   720  			LabelNames: []string{
   721  				"pyroscope_app",
   722  				"service_name",
   723  				"__profile_type__",
   724  				"__type__",
   725  				"__name__",
   726  			},
   727  		})
   728  
   729  		assert.NoError(t, err)
   730  		assert.Equal(t, want, got)
   731  	})
   732  }
   733  
   734  func Test_singleBlockQuerier_LabelNames(t *testing.T) {
   735  	ctx := context.Background()
   736  	reader, err := index.NewFileReader("testdata/01HA2V3CPSZ9E0HMQNNHH89WSS/index.tsdb")
   737  	assert.NoError(t, err)
   738  
   739  	q := &singleBlockQuerier{
   740  		metrics: NewBlocksMetrics(nil),
   741  		meta:    &block.Meta{ULID: ulid.MustParse("01HA2V3CPSZ9E0HMQNNHH89WSS")},
   742  		opened:  true, // Skip trying to open the block.
   743  		index:   reader,
   744  	}
   745  
   746  	t.Run("no matchers", func(t *testing.T) {
   747  		want := []string{
   748  			"__delta__",
   749  			"__name__",
   750  			"__period_type__",
   751  			"__period_unit__",
   752  			"__profile_type__",
   753  			"__service_name__",
   754  			"__type__",
   755  			"__unit__",
   756  			"foo",
   757  			"function",
   758  			"pyroscope_spy",
   759  			"service_name",
   760  			"target",
   761  			"version",
   762  		}
   763  
   764  		got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{
   765  			Matchers: []string{},
   766  		}))
   767  		assert.NoError(t, err)
   768  		assert.Equal(t, want, got.Msg.Names)
   769  	})
   770  
   771  	t.Run("empty matcher", func(t *testing.T) {
   772  		want := []string{
   773  			"__delta__",
   774  			"__name__",
   775  			"__period_type__",
   776  			"__period_unit__",
   777  			"__profile_type__",
   778  			"__service_name__",
   779  			"__type__",
   780  			"__unit__",
   781  			"foo",
   782  			"function",
   783  			"pyroscope_spy",
   784  			"service_name",
   785  			"target",
   786  			"version",
   787  		}
   788  
   789  		got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{
   790  			Matchers: []string{`{}`},
   791  		}))
   792  		assert.NoError(t, err)
   793  		assert.Equal(t, want, got.Msg.Names)
   794  	})
   795  
   796  	t.Run("single matcher", func(t *testing.T) {
   797  		want := []string{
   798  			"__delta__",
   799  			"__name__",
   800  			"__period_type__",
   801  			"__period_unit__",
   802  			"__profile_type__",
   803  			"__service_name__",
   804  			"__type__",
   805  			"__unit__",
   806  			"foo",
   807  			"function",
   808  			"pyroscope_spy",
   809  			"service_name",
   810  			"target",
   811  			"version",
   812  		}
   813  
   814  		got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{
   815  			Matchers: []string{`{__name__="process_cpu"}`},
   816  		}))
   817  		assert.NoError(t, err)
   818  		assert.Equal(t, want, got.Msg.Names)
   819  	})
   820  
   821  	t.Run("multiple matchers", func(t *testing.T) {
   822  		want := []string{
   823  			"__delta__",
   824  			"__name__",
   825  			"__profile_type__",
   826  			"__service_name__",
   827  			"__type__",
   828  			"__unit__",
   829  			"pyroscope_spy",
   830  			"service_name",
   831  			"target",
   832  			"version",
   833  		}
   834  
   835  		got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{
   836  			Matchers: []string{`{__name__="memory",__type__="alloc_objects"}`},
   837  		}))
   838  		assert.NoError(t, err)
   839  		assert.Equal(t, want, got.Msg.Names)
   840  	})
   841  
   842  	t.Run("ui plugin", func(t *testing.T) {
   843  		want := []string{
   844  			"__delta__",
   845  			"__name__",
   846  			"__period_type__",
   847  			"__period_unit__",
   848  			"__profile_type__",
   849  			"__service_name__",
   850  			"__type__",
   851  			"__unit__",
   852  			"foo",
   853  			"function",
   854  			"pyroscope_spy",
   855  			"service_name",
   856  		}
   857  
   858  		got, err := q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{
   859  			Matchers: []string{`{__profile_type__="process_cpu:cpu:nanoseconds:cpu:nanoseconds"}`, `{service_name="simple.golang.app"}`},
   860  		}))
   861  		assert.NoError(t, err)
   862  		assert.Equal(t, want, got.Msg.Names)
   863  	})
   864  }
   865  
   866  func Test_singleBlockQuerier_LabelValues(t *testing.T) {
   867  	ctx := context.Background()
   868  	reader, err := index.NewFileReader("testdata/01HA2V3CPSZ9E0HMQNNHH89WSS/index.tsdb")
   869  	assert.NoError(t, err)
   870  
   871  	q := &singleBlockQuerier{
   872  		metrics: NewBlocksMetrics(nil),
   873  		meta:    &block.Meta{ULID: ulid.MustParse("01HA2V3CPSZ9E0HMQNNHH89WSS")},
   874  		opened:  true, // Skip trying to open the block.
   875  		index:   reader,
   876  	}
   877  
   878  	t.Run("no matchers", func(t *testing.T) {
   879  		want := []string{
   880  			"pyroscope",
   881  			"simple.golang.app",
   882  		}
   883  
   884  		got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{
   885  			Matchers: []string{},
   886  			Name:     "service_name",
   887  		}))
   888  		assert.NoError(t, err)
   889  		assert.Equal(t, want, got.Msg.Names)
   890  	})
   891  
   892  	t.Run("empty matcher", func(t *testing.T) {
   893  		want := []string{
   894  			"pyroscope",
   895  			"simple.golang.app",
   896  		}
   897  
   898  		got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{
   899  			Matchers: []string{`{}`},
   900  			Name:     "service_name",
   901  		}))
   902  		assert.NoError(t, err)
   903  		assert.Equal(t, want, got.Msg.Names)
   904  	})
   905  
   906  	t.Run("single matcher", func(t *testing.T) {
   907  		want := []string{
   908  			"fast",
   909  			"slow",
   910  		}
   911  
   912  		got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{
   913  			Matchers: []string{`{service_name="simple.golang.app"}`},
   914  			Name:     "function",
   915  		}))
   916  		assert.NoError(t, err)
   917  		assert.Equal(t, want, got.Msg.Names)
   918  
   919  		// Pyroscope app shouldn't have any function label values.
   920  		got, err = q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{
   921  			Matchers: []string{`{service_name="pyroscope"}`},
   922  			Name:     "function",
   923  		}))
   924  		assert.NoError(t, err)
   925  		assert.Empty(t, got.Msg.Names)
   926  	})
   927  
   928  	t.Run("multiple matchers", func(t *testing.T) {
   929  		want := []string{
   930  			"fast",
   931  			"slow",
   932  		}
   933  
   934  		got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{
   935  			Matchers: []string{`{__profile_type__="process_cpu:cpu:nanoseconds:cpu:nanoseconds", service_name="simple.golang.app"}`},
   936  			Name:     "function",
   937  		}))
   938  		assert.NoError(t, err)
   939  		assert.Equal(t, want, got.Msg.Names)
   940  
   941  		// Memory profiles shouldn't have 'function' label values.
   942  		got, err = q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{
   943  			Matchers: []string{`{__profile_type__="memory:alloc_objects:count:space:bytes", service_name="simple.golang.app"}`},
   944  			Name:     "function",
   945  		}))
   946  		assert.NoError(t, err)
   947  		assert.Empty(t, got.Msg.Names)
   948  	})
   949  
   950  	t.Run("ui plugin", func(t *testing.T) {
   951  		want := []string{
   952  			"fast",
   953  			"slow",
   954  		}
   955  
   956  		got, err := q.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{
   957  			Matchers: []string{`{__profile_type__="process_cpu:cpu:nanoseconds:cpu:nanoseconds", service_name="simple.golang.app"}`},
   958  			Name:     "function",
   959  		}))
   960  		assert.NoError(t, err)
   961  		assert.Equal(t, want, got.Msg.Names)
   962  	})
   963  }
   964  
   965  func Test_singleBlockQuerier_ProfileTypes(t *testing.T) {
   966  	ctx := context.Background()
   967  	reader, err := index.NewFileReader("testdata/01HA2V3CPSZ9E0HMQNNHH89WSS/index.tsdb")
   968  	assert.NoError(t, err)
   969  
   970  	q := &singleBlockQuerier{
   971  		metrics: NewBlocksMetrics(nil),
   972  		meta:    &block.Meta{ULID: ulid.MustParse("01HA2V3CPSZ9E0HMQNNHH89WSS")},
   973  		opened:  true, // Skip trying to open the block.
   974  		index:   reader,
   975  	}
   976  
   977  	want := []*typesv1.ProfileType{
   978  		{
   979  			ID:         "block:contentions:count::",
   980  			Name:       "block",
   981  			SampleType: "contentions",
   982  			SampleUnit: "count",
   983  			PeriodType: "",
   984  			PeriodUnit: "",
   985  		},
   986  		{
   987  			ID:         "block:delay:nanoseconds::",
   988  			Name:       "block",
   989  			SampleType: "delay",
   990  			SampleUnit: "nanoseconds",
   991  			PeriodType: "",
   992  			PeriodUnit: "",
   993  		},
   994  		{
   995  			ID:         "goroutine:goroutines:count::",
   996  			Name:       "goroutine",
   997  			SampleType: "goroutines",
   998  			SampleUnit: "count",
   999  			PeriodType: "",
  1000  			PeriodUnit: "",
  1001  		},
  1002  		{
  1003  			ID:         "memory:alloc_objects:count::",
  1004  			Name:       "memory",
  1005  			SampleType: "alloc_objects",
  1006  			SampleUnit: "count",
  1007  			PeriodType: "",
  1008  			PeriodUnit: "",
  1009  		},
  1010  		{
  1011  			ID:         "memory:alloc_space:bytes::",
  1012  			Name:       "memory",
  1013  			SampleType: "alloc_space",
  1014  			SampleUnit: "bytes",
  1015  			PeriodType: "",
  1016  			PeriodUnit: "",
  1017  		},
  1018  		{
  1019  			ID:         "memory:inuse_objects:count::",
  1020  			Name:       "memory",
  1021  			SampleType: "inuse_objects",
  1022  			SampleUnit: "count",
  1023  			PeriodType: "",
  1024  			PeriodUnit: "",
  1025  		},
  1026  		{
  1027  			ID:         "memory:inuse_space:bytes::",
  1028  			Name:       "memory",
  1029  			SampleType: "inuse_space",
  1030  			SampleUnit: "bytes",
  1031  			PeriodType: "",
  1032  			PeriodUnit: "",
  1033  		},
  1034  		{
  1035  			ID:         "mutex:contentions:count::",
  1036  			Name:       "mutex",
  1037  			SampleType: "contentions",
  1038  			SampleUnit: "count",
  1039  			PeriodType: "",
  1040  			PeriodUnit: "",
  1041  		},
  1042  		{
  1043  			ID:         "mutex:delay:nanoseconds::",
  1044  			Name:       "mutex",
  1045  			SampleType: "delay",
  1046  			SampleUnit: "nanoseconds",
  1047  			PeriodType: "",
  1048  			PeriodUnit: "",
  1049  		},
  1050  		{
  1051  			ID:         "process_cpu:cpu:nanoseconds:cpu:nanoseconds",
  1052  			Name:       "process_cpu",
  1053  			SampleType: "cpu",
  1054  			SampleUnit: "nanoseconds",
  1055  			PeriodType: "cpu",
  1056  			PeriodUnit: "nanoseconds",
  1057  		},
  1058  	}
  1059  
  1060  	got, err := q.ProfileTypes(ctx, &connect.Request[ingesterv1.ProfileTypesRequest]{})
  1061  	assert.NoError(t, err)
  1062  	assert.Equal(t, want, got.Msg.ProfileTypes)
  1063  }
  1064  
  1065  func Benchmark_singleBlockQuerier_Series(b *testing.B) {
  1066  	const id = "01HA2V3CPSZ9E0HMQNNHH89WSS"
  1067  
  1068  	ctx := context.Background()
  1069  	reader, err := index.NewFileReader(fmt.Sprintf("testdata/%s/index.tsdb", id))
  1070  	assert.NoError(b, err)
  1071  
  1072  	q := &singleBlockQuerier{
  1073  		metrics: NewBlocksMetrics(nil),
  1074  		meta:    &block.Meta{ULID: ulid.MustParse(id)},
  1075  		opened:  true, // Skip trying to open the block.
  1076  		index:   reader,
  1077  	}
  1078  
  1079  	b.Run("multiple labels", func(b *testing.B) {
  1080  		for n := 0; n < b.N; n++ {
  1081  			q.Series(ctx, &ingesterv1.SeriesRequest{ //nolint:errcheck
  1082  				Matchers:   []string{`{__name__="block"}`},
  1083  				LabelNames: []string{"__name__"},
  1084  			})
  1085  		}
  1086  	})
  1087  
  1088  	b.Run("multiple labels with matcher", func(b *testing.B) {
  1089  		for n := 0; n < b.N; n++ {
  1090  			q.Series(ctx, &ingesterv1.SeriesRequest{ //nolint:errcheck
  1091  				Matchers:   []string{`{__name__="memory",__type__="alloc_objects"}`},
  1092  				LabelNames: []string{"__name__", "__type__"},
  1093  			})
  1094  		}
  1095  	})
  1096  
  1097  	b.Run("UI request", func(b *testing.B) {
  1098  		for n := 0; n < b.N; n++ {
  1099  			q.Series(ctx, &ingesterv1.SeriesRequest{ //nolint:errcheck
  1100  				Matchers:   []string{},
  1101  				LabelNames: []string{"pyroscope_app", "service_name", "__profile_type__", "__type__", "__name__"},
  1102  			})
  1103  		}
  1104  	})
  1105  }
  1106  
  1107  func Benchmark_singleBlockQuerier_LabelNames(b *testing.B) {
  1108  	ctx := context.Background()
  1109  	reader, err := index.NewFileReader("testdata/01HA2V3CPSZ9E0HMQNNHH89WSS/index.tsdb")
  1110  	assert.NoError(b, err)
  1111  
  1112  	q := &singleBlockQuerier{
  1113  		metrics: NewBlocksMetrics(nil),
  1114  		meta:    &block.Meta{ULID: ulid.MustParse("01HA2V3CPSZ9E0HMQNNHH89WSS")},
  1115  		opened:  true, // Skip trying to open the block.
  1116  		index:   reader,
  1117  	}
  1118  
  1119  	b.Run("multiple matchers", func(b *testing.B) {
  1120  		for n := 0; n < b.N; n++ {
  1121  			q.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{ //nolint:errcheck
  1122  				Matchers: []string{`{__profile_type__="process_cpu:cpu:nanoseconds:cpu:nanoseconds"}`, `{service_name="simple.golang.app"}`},
  1123  			}))
  1124  		}
  1125  	})
  1126  }
  1127  
  1128  func TestSelectMergeStacktraces(t *testing.T) {
  1129  	ctx := context.Background()
  1130  
  1131  	querier := newBlock(t, func() (res []*testhelper.ProfileBuilder) {
  1132  		for i := int64(1); i < 1001; i++ {
  1133  			res = append(res, testhelper.NewProfileBuilder(int64(time.Second)*i).
  1134  				CPUProfile().
  1135  				WithLabels(
  1136  					"job", "a",
  1137  				).ForStacktraceString("foo", "bar", "baz").AddSamples(1),
  1138  				testhelper.NewProfileBuilder(int64(time.Second*2)*i).
  1139  					CPUProfile().
  1140  					WithLabels(
  1141  						"job", "b",
  1142  					).ForStacktraceString("foo", "bar", "buzz").AddSamples(1),
  1143  				testhelper.NewProfileBuilder(int64(time.Second*3)*i).
  1144  					CPUProfile().
  1145  					WithLabels(
  1146  						"job", "c",
  1147  					).ForStacktraceString("foo", "bar").AddSamples(1))
  1148  		}
  1149  		return res
  1150  	})
  1151  
  1152  	err := querier.Open(ctx)
  1153  	require.NoError(t, err)
  1154  
  1155  	merge, err := querier.SelectMergeByStacktraces(ctx, &ingesterv1.SelectProfilesRequest{
  1156  		LabelSelector: `{}`,
  1157  		Type: &typesv1.ProfileType{
  1158  			ID:         "process_cpu:cpu:nanoseconds:cpu:nanoseconds",
  1159  			Name:       "process_cpu",
  1160  			SampleType: "cpu",
  1161  			SampleUnit: "nanoseconds",
  1162  			PeriodType: "cpu",
  1163  			PeriodUnit: "nanoseconds",
  1164  		},
  1165  		Start: 0,
  1166  		End:   int64(model.TimeFromUnixNano(math.MaxInt64)),
  1167  	}, 16<<10)
  1168  	require.NoError(t, err)
  1169  	expected := phlaremodel.Tree{}
  1170  	expected.InsertStack(1000, "baz", "bar", "foo")
  1171  	expected.InsertStack(1000, "buzz", "bar", "foo")
  1172  	expected.InsertStack(1000, "bar", "foo")
  1173  	require.Equal(t, expected.String(), merge.String())
  1174  	require.NoError(t, querier.Close())
  1175  }
  1176  
  1177  func TestSelectMergeLabels(t *testing.T) {
  1178  	ctx := context.Background()
  1179  
  1180  	querier := newBlock(t, func() (res []*testhelper.ProfileBuilder) {
  1181  		for i := int64(1); i < 6; i++ {
  1182  			res = append(res, testhelper.NewProfileBuilder(int64(time.Second)*i).
  1183  				CPUProfile().
  1184  				WithLabels(
  1185  					"job", "a",
  1186  				).ForStacktraceString("foo", "bar", "baz").AddSamples(1),
  1187  				testhelper.NewProfileBuilder(int64(time.Second)*i).
  1188  					CPUProfile().
  1189  					WithLabels(
  1190  						"job", "b",
  1191  					).ForStacktraceString("foo", "bar", "buzz").AddSamples(1),
  1192  				testhelper.NewProfileBuilder(int64(time.Second)*i).
  1193  					CPUProfile().
  1194  					WithLabels(
  1195  						"job", "c",
  1196  					).ForStacktraceString("foo", "bar").AddSamples(1))
  1197  		}
  1198  		return res
  1199  	})
  1200  
  1201  	err := querier.Open(ctx)
  1202  	require.NoError(t, err)
  1203  
  1204  	merge, err := querier.SelectMergeByLabels(ctx, &ingesterv1.SelectProfilesRequest{
  1205  		LabelSelector: `{}`,
  1206  		Type: &typesv1.ProfileType{
  1207  			ID:         "process_cpu:cpu:nanoseconds:cpu:nanoseconds",
  1208  			Name:       "process_cpu",
  1209  			SampleType: "cpu",
  1210  			SampleUnit: "nanoseconds",
  1211  			PeriodType: "cpu",
  1212  			PeriodUnit: "nanoseconds",
  1213  		},
  1214  		Start: 0,
  1215  		End:   int64(model.TimeFromUnixNano(math.MaxInt64)),
  1216  	}, nil, "job")
  1217  	require.NoError(t, err)
  1218  	expected := []*typesv1.Series{
  1219  		{
  1220  			Labels: phlaremodel.LabelsFromStrings("job", "a"),
  1221  			Points: genPoints(5),
  1222  		},
  1223  		{
  1224  			Labels: phlaremodel.LabelsFromStrings("job", "b"),
  1225  			Points: genPoints(5),
  1226  		},
  1227  		{
  1228  			Labels: phlaremodel.LabelsFromStrings("job", "c"),
  1229  			Points: genPoints(5),
  1230  		},
  1231  	}
  1232  	require.Equal(t, expected, merge)
  1233  	require.NoError(t, querier.Close())
  1234  }
  1235  
  1236  func TestSelectMergeLabels_StackTraceSelector(t *testing.T) {
  1237  	ctx := context.Background()
  1238  
  1239  	querier := newBlock(t, func() (res []*testhelper.ProfileBuilder) {
  1240  		for i := int64(1); i < 7; i++ {
  1241  			// Keep in mind that leaf is at location[0].
  1242  			res = append(res, testhelper.NewProfileBuilder(int64(time.Second)*i).
  1243  				CPUProfile().
  1244  				WithLabels("job", "a").
  1245  				ForStacktraceString("foo").AddSamples(1).
  1246  				ForStacktraceString("baz", "bar", "foo").AddSamples(1).
  1247  				ForStacktraceString("baz", "foo").AddSamples(1),
  1248  			)
  1249  		}
  1250  		return res
  1251  	})
  1252  
  1253  	err := querier.Open(ctx)
  1254  	require.NoError(t, err)
  1255  
  1256  	merge, err := querier.SelectMergeByLabels(ctx, &ingesterv1.SelectProfilesRequest{
  1257  		LabelSelector: `{}`,
  1258  		Type: &typesv1.ProfileType{
  1259  			ID:         "process_cpu:cpu:nanoseconds:cpu:nanoseconds",
  1260  			Name:       "process_cpu",
  1261  			SampleType: "cpu",
  1262  			SampleUnit: "nanoseconds",
  1263  			PeriodType: "cpu",
  1264  			PeriodUnit: "nanoseconds",
  1265  		},
  1266  		Start: 0,
  1267  		End:   int64(model.TimeFromUnixNano(math.MaxInt64)),
  1268  	}, &typesv1.StackTraceSelector{
  1269  		CallSite: []*typesv1.Location{
  1270  			{Name: "foo"},
  1271  			{Name: "bar"},
  1272  		},
  1273  	}, "job")
  1274  	require.NoError(t, err)
  1275  	expected := []*typesv1.Series{
  1276  		{
  1277  			Labels: phlaremodel.LabelsFromStrings("job", "a"),
  1278  			Points: genPoints(6),
  1279  		},
  1280  	}
  1281  	require.Equal(t, expected, merge)
  1282  	require.NoError(t, querier.Close())
  1283  }
  1284  
  1285  func genPoints(count int) []*typesv1.Point {
  1286  	points := make([]*typesv1.Point, 0, count)
  1287  	for i := 1; i < count+1; i++ {
  1288  		points = append(points, &typesv1.Point{
  1289  			Timestamp:   int64(model.TimeFromUnixNano(int64(time.Second * time.Duration(i)))),
  1290  			Value:       1,
  1291  			Annotations: []*typesv1.ProfileAnnotation{},
  1292  		})
  1293  	}
  1294  	return points
  1295  }
  1296  
  1297  func TestSelectMergeByStacktracesRace(t *testing.T) {
  1298  	testSelectMergeByStacktracesRace(t, 30)
  1299  }
  1300  
  1301  func BenchmarkSelectMergeByStacktracesRace(b *testing.B) {
  1302  	testSelectMergeByStacktracesRace(b, b.N)
  1303  }
  1304  
  1305  func testSelectMergeByStacktracesRace(t testing.TB, times int) {
  1306  	defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  1307  
  1308  	ctx := context.Background()
  1309  
  1310  	querier := newBlock(t, func() []*testhelper.ProfileBuilder {
  1311  		return []*testhelper.ProfileBuilder{
  1312  			testhelper.NewProfileBuilder(int64(time.Second*1)).
  1313  				CPUProfile().
  1314  				WithLabels(
  1315  					"job", "a",
  1316  				).ForStacktraceString("foo", "bar", "baz").AddSamples(1),
  1317  			testhelper.NewProfileBuilder(int64(time.Second*2)).
  1318  				CPUProfile().
  1319  				WithLabels(
  1320  					"job", "b",
  1321  				).ForStacktraceString("foo", "bar", "baz").AddSamples(1),
  1322  			testhelper.NewProfileBuilder(int64(time.Second*3)).
  1323  				CPUProfile().
  1324  				WithLabels(
  1325  					"job", "c",
  1326  				).ForStacktraceString("foo", "bar", "baz").AddSamples(1),
  1327  		}
  1328  	})
  1329  
  1330  	err := querier.Open(ctx)
  1331  	require.NoError(t, err)
  1332  	g, ctx := errgroup.WithContext(ctx)
  1333  	tree := new(phlaremodel.Tree)
  1334  	var m sync.Mutex
  1335  
  1336  	if b, ok := t.(*testing.B); ok {
  1337  		b.ResetTimer()
  1338  		b.ReportAllocs()
  1339  	}
  1340  
  1341  	for i := 0; i < times; i++ {
  1342  		g.Go(func() error {
  1343  			it, err := querier.SelectMatchingProfiles(ctx, &ingesterv1.SelectProfilesRequest{
  1344  				LabelSelector: `{}`,
  1345  				Type: &typesv1.ProfileType{
  1346  					ID:         "process_cpu:cpu:nanoseconds:cpu:nanoseconds",
  1347  					Name:       "process_cpu",
  1348  					SampleType: "cpu",
  1349  					SampleUnit: "nanoseconds",
  1350  					PeriodType: "cpu",
  1351  					PeriodUnit: "nanoseconds",
  1352  				},
  1353  				Start: 0,
  1354  				End:   int64(model.TimeFromUnixNano(math.MaxInt64)),
  1355  			})
  1356  			if err != nil {
  1357  				return err
  1358  			}
  1359  			defer it.Close()
  1360  			for it.Next() {
  1361  			}
  1362  			return nil
  1363  		})
  1364  		g.Go(func() error {
  1365  			merge, err := querier.SelectMergeByStacktraces(ctx, &ingesterv1.SelectProfilesRequest{
  1366  				LabelSelector: `{}`,
  1367  				Type: &typesv1.ProfileType{
  1368  					ID:         "process_cpu:cpu:nanoseconds:cpu:nanoseconds",
  1369  					Name:       "process_cpu",
  1370  					SampleType: "cpu",
  1371  					SampleUnit: "nanoseconds",
  1372  					PeriodType: "cpu",
  1373  					PeriodUnit: "nanoseconds",
  1374  				},
  1375  				Start: 0,
  1376  				End:   int64(model.TimeFromUnixNano(math.MaxInt64)),
  1377  			}, 16<<10)
  1378  			if err != nil {
  1379  				return err
  1380  			}
  1381  			m.Lock()
  1382  			tree.Merge(merge)
  1383  			m.Unlock()
  1384  			return nil
  1385  		})
  1386  	}
  1387  
  1388  	require.NoError(t, g.Wait())
  1389  	require.NoError(t, querier.Close())
  1390  }
  1391  
  1392  func TestBlockMeta_loadsMetasIndividually(t *testing.T) {
  1393  	path := testDataPath
  1394  	bucket, err := filesystem.NewBucket(path)
  1395  	require.NoError(t, err)
  1396  
  1397  	ctx := context.Background()
  1398  	blockQuerier := NewBlockQuerier(ctx, bucket)
  1399  	metas, err := blockQuerier.BlockMetas(ctx)
  1400  	require.NoError(t, err)
  1401  	require.NotEmpty(t, metas)
  1402  
  1403  	for _, meta := range metas {
  1404  		singleMeta, err := blockQuerier.BlockMeta(ctx, meta.ULID.String())
  1405  		require.NoError(t, err)
  1406  
  1407  		require.Equal(t, meta, singleMeta)
  1408  	}
  1409  }