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

     1  package tsdb
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/prometheus/common/model"
     9  	"github.com/prometheus/prometheus/model/labels"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    13  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    14  	"github.com/grafana/pyroscope/pkg/phlaredb/tsdb/index"
    15  	"github.com/grafana/pyroscope/pkg/phlaredb/tsdb/shard"
    16  )
    17  
    18  func Test_BitPrefixGetShards(t *testing.T) {
    19  	for _, tt := range []struct {
    20  		total    uint32
    21  		filter   bool
    22  		shard    *shard.Annotation
    23  		expected []uint32
    24  	}{
    25  		// equal factors
    26  		{16, false, &shard.Annotation{Shard: 0, Of: 16}, []uint32{0}},
    27  		{16, false, &shard.Annotation{Shard: 4, Of: 16}, []uint32{4}},
    28  		{16, false, &shard.Annotation{Shard: 15, Of: 16}, []uint32{15}},
    29  
    30  		// idx factor a larger factor of 2
    31  		{32, false, &shard.Annotation{Shard: 0, Of: 16}, []uint32{0, 1}},
    32  		{32, false, &shard.Annotation{Shard: 4, Of: 16}, []uint32{8, 9}},
    33  		{32, false, &shard.Annotation{Shard: 15, Of: 16}, []uint32{30, 31}},
    34  		{64, false, &shard.Annotation{Shard: 15, Of: 16}, []uint32{60, 61, 62, 63}},
    35  
    36  		// // idx factor a smaller factor of 2
    37  		{8, true, &shard.Annotation{Shard: 0, Of: 16}, []uint32{0}},
    38  		{8, true, &shard.Annotation{Shard: 4, Of: 16}, []uint32{2}},
    39  		{8, true, &shard.Annotation{Shard: 15, Of: 16}, []uint32{7}},
    40  	} {
    41  		tt := tt
    42  		t.Run(tt.shard.String()+fmt.Sprintf("_total_%d", tt.total), func(t *testing.T) {
    43  			ii, err := NewBitPrefixWithShards(tt.total)
    44  			require.Nil(t, err)
    45  			res, filter := ii.getShards(tt.shard)
    46  			resInt := []uint32{}
    47  			for _, r := range res {
    48  				resInt = append(resInt, r.shard)
    49  			}
    50  			require.Equal(t, tt.filter, filter)
    51  			require.Equal(t, tt.expected, resInt)
    52  		})
    53  	}
    54  }
    55  
    56  func Test_BitPrefixValidateShards(t *testing.T) {
    57  	ii, err := NewBitPrefixWithShards(32)
    58  	require.Nil(t, err)
    59  	require.NoError(t, ii.validateShard(&shard.Annotation{Shard: 1, Of: 16}))
    60  	require.Error(t, ii.validateShard(&shard.Annotation{Shard: 1, Of: 15}))
    61  }
    62  
    63  func Test_BitPrefixCreation(t *testing.T) {
    64  	// non factor of 2 shard factor
    65  	_, err := NewBitPrefixWithShards(6)
    66  	require.Error(t, err)
    67  
    68  	// valid shard factor
    69  	_, err = NewBitPrefixWithShards(4)
    70  	require.Nil(t, err)
    71  }
    72  
    73  func Test_BitPrefixDeleteAddLoopkup(t *testing.T) {
    74  	index, err := NewBitPrefixWithShards(DefaultIndexShards)
    75  	require.Nil(t, err)
    76  	lbs := []*typesv1.LabelPair{
    77  		{Name: "foo", Value: "foo"},
    78  		{Name: "bar", Value: "bar"},
    79  		{Name: "buzz", Value: "buzz"},
    80  	}
    81  	sort.Sort(phlaremodel.Labels(lbs))
    82  
    83  	index.Add(lbs, model.Fingerprint((phlaremodel.Labels(lbs).Hash())))
    84  	index.Delete(lbs, model.Fingerprint(phlaremodel.Labels(lbs).Hash()))
    85  	ids, err := index.Lookup([]*labels.Matcher{
    86  		labels.MustNewMatcher(labels.MatchEqual, "foo", "foo"),
    87  	}, nil)
    88  	require.NoError(t, err)
    89  	require.Len(t, ids, 0)
    90  }
    91  
    92  func Test_BitPrefix_hash_mapping(t *testing.T) {
    93  	lbs := []*typesv1.LabelPair{
    94  		{Name: "compose_project", Value: "loki-boltdb-storage-s3"},
    95  		{Name: "compose_service", Value: "ingester-2"},
    96  		{Name: "container_name", Value: "loki-boltdb-storage-s3_ingester-2_1"},
    97  		{Name: "filename", Value: "/var/log/docker/790fef4c6a587c3b386fe85c07e03f3a1613f4929ca3abaa4880e14caadb5ad1/json.log"},
    98  		{Name: "host", Value: "docker-desktop"},
    99  		{Name: "source", Value: "stderr"},
   100  	}
   101  
   102  	// for _, shard := range []uint32{2, 4, 8, 16, 32, 64, 128} {
   103  	for _, shardID := range []uint32{2} {
   104  		t.Run(fmt.Sprintf("%d", shardID), func(t *testing.T) {
   105  			ii, err := NewBitPrefixWithShards(shardID)
   106  			require.Nil(t, err)
   107  
   108  			requestedFactor := 16
   109  
   110  			fp := model.Fingerprint(phlaremodel.Labels(lbs).Hash())
   111  			ii.Add(lbs, fp)
   112  
   113  			requiredBits := index.NewShard(0, uint32(requestedFactor)).RequiredBits()
   114  			expShard := uint32(phlaremodel.Labels(lbs).Hash() >> (64 - requiredBits))
   115  
   116  			res, err := ii.Lookup(
   117  				[]*labels.Matcher{{
   118  					Type:  labels.MatchEqual,
   119  					Name:  "compose_project",
   120  					Value: "loki-boltdb-storage-s3",
   121  				}},
   122  				&shard.Annotation{
   123  					Shard: int(expShard),
   124  					Of:    requestedFactor,
   125  				},
   126  			)
   127  			require.NoError(t, err)
   128  			require.Len(t, res, 1)
   129  			require.Equal(t, fp, res[0])
   130  		})
   131  	}
   132  }
   133  
   134  func Test_BitPrefixNoMatcherLookup(t *testing.T) {
   135  	lbs := []*typesv1.LabelPair{
   136  		{Name: "foo", Value: "bar"},
   137  		{Name: "hi", Value: "hello"},
   138  	}
   139  	// with no shard param
   140  	ii, err := NewBitPrefixWithShards(16)
   141  	require.Nil(t, err)
   142  	fp := model.Fingerprint(phlaremodel.Labels(lbs).Hash())
   143  	ii.Add(lbs, fp)
   144  	ids, err := ii.Lookup(nil, nil)
   145  	require.Nil(t, err)
   146  	require.Equal(t, fp, ids[0])
   147  
   148  	// with shard param
   149  	ii, err = NewBitPrefixWithShards(16)
   150  	require.Nil(t, err)
   151  	expShard := uint32(fp >> (64 - index.NewShard(0, 16).RequiredBits()))
   152  	ii.Add(lbs, fp)
   153  	ids, err = ii.Lookup(nil, &shard.Annotation{Shard: int(expShard), Of: 16})
   154  	require.Nil(t, err)
   155  	require.Equal(t, fp, ids[0])
   156  }
   157  
   158  func Test_BitPrefixConsistentMapping(t *testing.T) {
   159  	a, err := NewBitPrefixWithShards(16)
   160  	require.Nil(t, err)
   161  	b, err := NewBitPrefixWithShards(32)
   162  	require.Nil(t, err)
   163  
   164  	for i := 0; i < 100; i++ {
   165  		lbs := []*typesv1.LabelPair{
   166  			{Name: "foo", Value: "bar"},
   167  			{Name: "hi", Value: fmt.Sprint(i)},
   168  		}
   169  
   170  		fp := model.Fingerprint(phlaremodel.Labels(lbs).Hash())
   171  		a.Add(lbs, fp)
   172  		b.Add(lbs, fp)
   173  	}
   174  
   175  	shardMax := 8
   176  	for i := 0; i < shardMax; i++ {
   177  		shard := &shard.Annotation{
   178  			Shard: i,
   179  			Of:    shardMax,
   180  		}
   181  
   182  		aIDs, err := a.Lookup([]*labels.Matcher{
   183  			labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
   184  		}, shard)
   185  		require.Nil(t, err)
   186  
   187  		bIDs, err := b.Lookup([]*labels.Matcher{
   188  			labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
   189  		}, shard)
   190  		require.Nil(t, err)
   191  
   192  		sorter := func(xs []model.Fingerprint) {
   193  			sort.Slice(xs, func(i, j int) bool {
   194  				return xs[i] < xs[j]
   195  			})
   196  		}
   197  		sorter(aIDs)
   198  		sorter(bIDs)
   199  
   200  		require.Equal(t, aIDs, bIDs, "incorrect shard mapping for shard %v", shard)
   201  	}
   202  }