github.com/grafana/pyroscope@v1.18.0/pkg/segmentwriter/memdb/profile_index_test.go (about)

     1  package memdb
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sort"
     8  	"sync"
     9  	"testing"
    10  
    11  	"github.com/google/uuid"
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"github.com/prometheus/common/model"
    14  	"github.com/prometheus/prometheus/model/labels"
    15  	"github.com/prometheus/prometheus/promql/parser"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  	"google.golang.org/grpc/codes"
    19  	"google.golang.org/grpc/status"
    20  
    21  	ingestv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1"
    22  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    23  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    24  	"github.com/grafana/pyroscope/pkg/phlaredb"
    25  	v1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1"
    26  	"github.com/grafana/pyroscope/pkg/phlaredb/tsdb/index"
    27  )
    28  
    29  func TestIndex(t *testing.T) {
    30  	a := newProfileIndex(NewHeadMetricsWithPrefix(prometheus.NewRegistry(), ""))
    31  	var wg sync.WaitGroup
    32  	for i := 0; i < 10; i++ {
    33  		wg.Add(1)
    34  		go func() {
    35  			defer wg.Done()
    36  			for j := 0; j < 10; j++ {
    37  				lb1 := phlaremodel.Labels([]*typesv1.LabelPair{
    38  					{Name: "__name__", Value: "memory"},
    39  					{Name: "__sample__type__", Value: "bytes"},
    40  					{Name: "__profile_type__", Value: "::::"},
    41  					{Name: "bar", Value: fmt.Sprint(j)},
    42  				})
    43  				sort.Sort(lb1)
    44  				lb2 := phlaremodel.Labels([]*typesv1.LabelPair{
    45  					{Name: "__name__", Value: "memory"},
    46  					{Name: "__sample__type__", Value: "count"},
    47  					{Name: "__profile_type__", Value: "::::"},
    48  					{Name: "bar", Value: fmt.Sprint(j)},
    49  				})
    50  				sort.Sort(lb2)
    51  
    52  				for k := int64(0); k < 10; k++ {
    53  					id := uuid.New()
    54  					a.Add(&v1.InMemoryProfile{
    55  						ID:                id,
    56  						TimeNanos:         k,
    57  						SeriesFingerprint: model.Fingerprint(lb1.Hash()),
    58  					}, lb1, "memory")
    59  					a.Add(&v1.InMemoryProfile{
    60  						ID:                id,
    61  						TimeNanos:         k,
    62  						SeriesFingerprint: model.Fingerprint(lb2.Hash()),
    63  					}, lb2, "memory")
    64  				}
    65  			}
    66  		}()
    67  	}
    68  	wg.Wait()
    69  
    70  	// Testing Matching
    71  	fps, err := selectMatchingFPs(a, &ingestv1.SelectProfilesRequest{
    72  		LabelSelector: `memory{bar=~"[0-9]", buzz!="bar"}`,
    73  		Type:          &typesv1.ProfileType{},
    74  	})
    75  	require.NoError(t, err)
    76  	require.Len(t, fps, 20)
    77  
    78  	names, err := a.ix.LabelNames(nil)
    79  	require.NoError(t, err)
    80  	require.Equal(t, []string{"__name__", "__profile_type__", "__sample__type__", "bar"}, names)
    81  
    82  	values, err := a.ix.LabelValues("__sample__type__", nil)
    83  	require.NoError(t, err)
    84  	require.Equal(t, []string{"bytes", "count"}, values)
    85  	values, err = a.ix.LabelValues("bar", nil)
    86  	require.NoError(t, err)
    87  	require.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}, values)
    88  }
    89  
    90  func selectMatchingFPs(pi *profilesIndex, params *ingestv1.SelectProfilesRequest) ([]model.Fingerprint, error) {
    91  	selectors, err := parser.ParseMetricSelector(params.LabelSelector)
    92  	if err != nil {
    93  		return nil, status.Error(codes.InvalidArgument, "failed to parse label selectors: "+err.Error())
    94  	}
    95  	if params.Type == nil {
    96  		return nil, errors.New("no profileType given")
    97  	}
    98  	selectors = append(selectors, phlaremodel.SelectorFromProfileType(params.Type))
    99  
   100  	filters, matchers := phlaredb.SplitFiltersAndMatchers(selectors)
   101  	ids, err := pi.ix.Lookup(matchers, nil)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	pi.mutex.RLock()
   107  	defer pi.mutex.RUnlock()
   108  
   109  	// filter fingerprints that no longer exist or don't match the filters
   110  	var idx int
   111  outer:
   112  	for _, fp := range ids {
   113  		profile, ok := pi.profilesPerFP[fp]
   114  		if !ok {
   115  			// If a profile labels is missing here, it has already been flushed
   116  			// and is supposed to be picked up from storage by querier
   117  			continue
   118  		}
   119  		for _, filter := range filters {
   120  			if !filter.Matches(profile.lbs.Get(filter.Name)) {
   121  				continue outer
   122  			}
   123  		}
   124  
   125  		// keep this one
   126  		ids[idx] = fp
   127  		idx++
   128  	}
   129  
   130  	return ids[:idx], nil
   131  }
   132  
   133  func TestWriteRead(t *testing.T) {
   134  	a := newProfileIndex(NewHeadMetricsWithPrefix(prometheus.NewRegistry(), ""))
   135  
   136  	for j := 0; j < 10; j++ {
   137  		lb1 := phlaremodel.Labels([]*typesv1.LabelPair{
   138  			{Name: "__name__", Value: "memory"},
   139  			{Name: "__sample__type__", Value: "bytes"},
   140  			{Name: "bar", Value: fmt.Sprint(j)},
   141  		})
   142  		sort.Sort(lb1)
   143  		lb2 := phlaremodel.Labels([]*typesv1.LabelPair{
   144  			{Name: "__name__", Value: "memory"},
   145  			{Name: "__sample__type__", Value: "count"},
   146  			{Name: "bar", Value: fmt.Sprint(j)},
   147  		})
   148  		sort.Sort(lb2)
   149  
   150  		for k := int64(0); k < 10; k++ {
   151  			id := uuid.New()
   152  			a.Add(&v1.InMemoryProfile{
   153  				ID:                id,
   154  				TimeNanos:         k,
   155  				SeriesFingerprint: model.Fingerprint(lb1.Hash()),
   156  			}, lb1, "memory")
   157  			a.Add(&v1.InMemoryProfile{
   158  				ID:                id,
   159  				TimeNanos:         k,
   160  				SeriesFingerprint: model.Fingerprint(lb2.Hash()),
   161  			}, lb2, "memory")
   162  		}
   163  	}
   164  
   165  	indexData, _, err := a.Flush(context.Background())
   166  	require.NoError(t, err)
   167  
   168  	r, err := index.NewReader(index.RealByteSlice(indexData))
   169  	require.NoError(t, err)
   170  
   171  	names, err := r.LabelNames()
   172  	require.NoError(t, err)
   173  	require.Equal(t, []string{"__name__", "__sample__type__", "bar"}, names)
   174  
   175  	values, err := r.LabelValues("bar")
   176  	require.NoError(t, err)
   177  	require.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}, values)
   178  
   179  	from, through := r.Bounds()
   180  	require.Equal(t, int64(0), from)
   181  	require.Equal(t, int64(9), through)
   182  	p, err := r.Postings("__name__", nil, "memory")
   183  	lbls := make(phlaremodel.Labels, 2)
   184  	chks := make([]index.ChunkMeta, 1)
   185  	require.NoError(t, err)
   186  	for p.Next() {
   187  		fp, err := r.Series(p.At(), &lbls, &chks)
   188  		require.NoError(t, err)
   189  		require.Equal(t, lbls.Hash(), fp)
   190  		require.Equal(t, "memory", lbls.Get("__name__"))
   191  		require.Equal(t, 3, len(lbls))
   192  		require.Equal(t, 1, len(chks))
   193  		require.Equal(t, int64(0), chks[0].MinTime)
   194  		require.Equal(t, int64(9), chks[0].MaxTime)
   195  	}
   196  }
   197  
   198  func TestQueryIndex(t *testing.T) {
   199  	a := newProfileIndex(NewHeadMetricsWithPrefix(prometheus.NewRegistry(), ""))
   200  
   201  	for j := 0; j < 10; j++ {
   202  		lb1 := phlaremodel.Labels([]*typesv1.LabelPair{
   203  			{Name: "__name__", Value: "memory"},
   204  			{Name: "__sample__type__", Value: "bytes"},
   205  			{Name: "bar", Value: fmt.Sprint(j)},
   206  		})
   207  		sort.Sort(lb1)
   208  		lb2 := phlaremodel.Labels([]*typesv1.LabelPair{
   209  			{Name: "__name__", Value: "memory"},
   210  			{Name: "__sample__type__", Value: "count"},
   211  			{Name: "bar", Value: fmt.Sprint(j)},
   212  		})
   213  		sort.Sort(lb2)
   214  
   215  		for k := int64(0); k < 10; k++ {
   216  			id := uuid.New()
   217  			a.Add(&v1.InMemoryProfile{
   218  				ID:                id,
   219  				TimeNanos:         k,
   220  				SeriesFingerprint: model.Fingerprint(lb1.Hash()),
   221  			}, lb1, "memory")
   222  			a.Add(&v1.InMemoryProfile{
   223  				ID:                id,
   224  				TimeNanos:         k,
   225  				SeriesFingerprint: model.Fingerprint(lb2.Hash()),
   226  			}, lb2, "memory")
   227  		}
   228  	}
   229  
   230  	indexData, _, err := a.Flush(context.Background())
   231  	require.NoError(t, err)
   232  
   233  	r, err := index.NewReader(index.RealByteSlice(indexData))
   234  	require.NoError(t, err)
   235  
   236  	p, err := phlaredb.PostingsForMatchers(r, nil, labels.MustNewMatcher(labels.MatchRegexp, "bar", "(1|2)"))
   237  	require.NoError(t, err)
   238  
   239  	lbls := make(phlaremodel.Labels, 3)
   240  	chks := make([]index.ChunkMeta, 1)
   241  	for p.Next() {
   242  		fp, err := r.Series(p.At(), &lbls, &chks)
   243  		require.NoError(t, err)
   244  		require.Equal(t, lbls.Hash(), fp)
   245  		require.Equal(t, 3, len(lbls))
   246  
   247  		require.Equal(t, "memory", lbls.Get("__name__"))
   248  		require.True(t, lbls.Get("bar") == "1" || lbls.Get("bar") == "2")
   249  
   250  		require.Equal(t, 1, len(chks))
   251  		require.Equal(t, int64(0), chks[0].MinTime)
   252  		require.Equal(t, int64(9), chks[0].MaxTime)
   253  	}
   254  }
   255  
   256  func TestProfileTypeNames(t *testing.T) {
   257  	a := newProfileIndex(NewHeadMetricsWithPrefix(prometheus.NewRegistry(), ""))
   258  
   259  	for j := 0; j < 5; j++ {
   260  		lb1 := phlaremodel.Labels([]*typesv1.LabelPair{
   261  			{Name: "__name__", Value: "cpu"},
   262  			{Name: phlaremodel.LabelNameProfileType, Value: fmt.Sprintf("test-profile-type-%d", j)},
   263  		})
   264  		sort.Sort(lb1)
   265  		a.Add(&v1.InMemoryProfile{
   266  			ID:                uuid.New(),
   267  			TimeNanos:         0,
   268  			SeriesFingerprint: model.Fingerprint(lb1.Hash()),
   269  		}, lb1, "cpu")
   270  	}
   271  	names, err := a.profileTypeNames()
   272  	require.NoError(t, err)
   273  	require.Equal(t, []string{"test-profile-type-0", "test-profile-type-1", "test-profile-type-2", "test-profile-type-3", "test-profile-type-4"}, names)
   274  }
   275  
   276  func TestIndexAddOutOfOrder(t *testing.T) {
   277  	a := newProfileIndex(NewHeadMetricsWithPrefix(prometheus.NewRegistry(), ""))
   278  
   279  	lb1 := phlaremodel.Labels([]*typesv1.LabelPair{
   280  		{Name: "__name__", Value: "memory"},
   281  		{Name: "__sample__type__", Value: "bytes"},
   282  		{Name: "bar", Value: "1"},
   283  	})
   284  	sort.Sort(lb1)
   285  
   286  	lb2 := phlaremodel.Labels([]*typesv1.LabelPair{
   287  		{Name: "__name__", Value: "memory"},
   288  		{Name: "__sample__type__", Value: "bytes"},
   289  		{Name: "bar", Value: "2"},
   290  	})
   291  	sort.Sort(lb2)
   292  
   293  	a.Add(&v1.InMemoryProfile{
   294  		ID:                uuid.New(),
   295  		TimeNanos:         239,
   296  		SeriesFingerprint: model.Fingerprint(lb2.Hash()),
   297  	}, lb2, "memory")
   298  
   299  	ts := []uint64{10, 20, 0}
   300  
   301  	for _, t := range ts {
   302  		a.Add(&v1.InMemoryProfile{
   303  			ID:                uuid.New(),
   304  			TimeNanos:         int64(t),
   305  			SeriesFingerprint: model.Fingerprint(lb1.Hash()),
   306  		}, lb1, "memory")
   307  	}
   308  
   309  	a.Add(&v1.InMemoryProfile{
   310  		ID:                uuid.New(),
   311  		TimeNanos:         238,
   312  		SeriesFingerprint: model.Fingerprint(lb2.Hash()),
   313  	}, lb2, "memory")
   314  
   315  	_, profiles, err := a.Flush(context.Background())
   316  	require.NoError(t, err)
   317  	assert.Equal(t, 5, len(profiles))
   318  	expectedTS := []int64{0, 10, 20, 238, 239}
   319  	expectedSeriesIndex := []uint32{0, 0, 0, 1, 1}
   320  	for i := range profiles {
   321  		assert.Equal(t, expectedTS[i], profiles[i].TimeNanos)
   322  		assert.Equal(t, expectedSeriesIndex[i], profiles[i].SeriesIndex)
   323  	}
   324  }