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

     1  package phlaredb
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/google/uuid"
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	"github.com/prometheus/common/model"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	ingestv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1"
    17  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    18  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    19  	v1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1"
    20  	"github.com/grafana/pyroscope/pkg/phlaredb/tsdb/index"
    21  	"github.com/grafana/pyroscope/pkg/pprof/testhelper"
    22  )
    23  
    24  func TestIndex(t *testing.T) {
    25  	a, err := newProfileIndex(16, newHeadMetrics(prometheus.NewRegistry()))
    26  	require.NoError(t, err)
    27  	var wg sync.WaitGroup
    28  	for i := 0; i < 10; i++ {
    29  		wg.Add(1)
    30  		go func() {
    31  			defer wg.Done()
    32  			for j := 0; j < 10; j++ {
    33  				lb1 := phlaremodel.Labels([]*typesv1.LabelPair{
    34  					{Name: "__name__", Value: "memory"},
    35  					{Name: "__sample__type__", Value: "bytes"},
    36  					{Name: "__profile_type__", Value: "::::"},
    37  					{Name: "bar", Value: fmt.Sprint(j)},
    38  				})
    39  				sort.Sort(lb1)
    40  				lb2 := phlaremodel.Labels([]*typesv1.LabelPair{
    41  					{Name: "__name__", Value: "memory"},
    42  					{Name: "__sample__type__", Value: "count"},
    43  					{Name: "__profile_type__", Value: "::::"},
    44  					{Name: "bar", Value: fmt.Sprint(j)},
    45  				})
    46  				sort.Sort(lb2)
    47  
    48  				for k := int64(0); k < 10; k++ {
    49  					id := uuid.New()
    50  					a.Add(&v1.InMemoryProfile{
    51  						ID:                id,
    52  						TimeNanos:         k,
    53  						SeriesFingerprint: model.Fingerprint(lb1.Hash()),
    54  					}, lb1, "memory")
    55  					a.Add(&v1.InMemoryProfile{
    56  						ID:                id,
    57  						TimeNanos:         k,
    58  						SeriesFingerprint: model.Fingerprint(lb2.Hash()),
    59  					}, lb2, "memory")
    60  				}
    61  			}
    62  		}()
    63  	}
    64  	wg.Wait()
    65  
    66  	// Testing Matching
    67  	ctx := testContext(t)
    68  	fps, err := a.selectMatchingFPs(ctx, &ingestv1.SelectProfilesRequest{
    69  		LabelSelector: `memory{bar=~"[0-9]", buzz!="bar"}`,
    70  		Type:          &typesv1.ProfileType{},
    71  	})
    72  	require.NoError(t, err)
    73  	require.Len(t, fps, 20)
    74  
    75  	names, err := a.ix.LabelNames(nil)
    76  	require.NoError(t, err)
    77  	require.Equal(t, []string{"__name__", "__profile_type__", "__sample__type__", "bar"}, names)
    78  
    79  	values, err := a.ix.LabelValues("__sample__type__", nil)
    80  	require.NoError(t, err)
    81  	require.Equal(t, []string{"bytes", "count"}, values)
    82  	values, err = a.ix.LabelValues("bar", nil)
    83  	require.NoError(t, err)
    84  	require.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}, values)
    85  }
    86  
    87  func TestWriteRead(t *testing.T) {
    88  	a, err := newProfileIndex(32, newHeadMetrics(prometheus.NewRegistry()))
    89  	require.NoError(t, err)
    90  
    91  	for j := 0; j < 10; j++ {
    92  		lb1 := phlaremodel.Labels([]*typesv1.LabelPair{
    93  			{Name: "__name__", Value: "memory"},
    94  			{Name: "__sample__type__", Value: "bytes"},
    95  			{Name: "bar", Value: fmt.Sprint(j)},
    96  		})
    97  		sort.Sort(lb1)
    98  		lb2 := phlaremodel.Labels([]*typesv1.LabelPair{
    99  			{Name: "__name__", Value: "memory"},
   100  			{Name: "__sample__type__", Value: "count"},
   101  			{Name: "bar", Value: fmt.Sprint(j)},
   102  		})
   103  		sort.Sort(lb2)
   104  
   105  		for k := int64(0); k < 10; k++ {
   106  			id := uuid.New()
   107  			a.Add(&v1.InMemoryProfile{
   108  				ID:                id,
   109  				TimeNanos:         k,
   110  				SeriesFingerprint: model.Fingerprint(lb1.Hash()),
   111  			}, lb1, "memory")
   112  			a.Add(&v1.InMemoryProfile{
   113  				ID:                id,
   114  				TimeNanos:         k,
   115  				SeriesFingerprint: model.Fingerprint(lb2.Hash()),
   116  			}, lb2, "memory")
   117  		}
   118  	}
   119  
   120  	tmpFile := t.TempDir() + "/test.db"
   121  	_, err = a.writeTo(context.Background(), tmpFile)
   122  	require.NoError(t, err)
   123  
   124  	r, err := index.NewFileReader(tmpFile)
   125  	require.NoError(t, err)
   126  
   127  	names, err := r.LabelNames()
   128  	require.NoError(t, err)
   129  	require.Equal(t, []string{"__name__", "__sample__type__", "bar"}, names)
   130  
   131  	values, err := r.LabelValues("bar")
   132  	require.NoError(t, err)
   133  	require.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}, values)
   134  
   135  	from, through := r.Bounds()
   136  	require.Equal(t, int64(0), from)
   137  	require.Equal(t, int64(9), through)
   138  	p, err := r.Postings("__name__", nil, "memory")
   139  	lbls := make(phlaremodel.Labels, 2)
   140  	chks := make([]index.ChunkMeta, 1)
   141  	require.NoError(t, err)
   142  	for p.Next() {
   143  		fp, err := r.Series(p.At(), &lbls, &chks)
   144  		require.NoError(t, err)
   145  		require.Equal(t, lbls.Hash(), fp)
   146  		require.Equal(t, "memory", lbls.Get("__name__"))
   147  		require.Equal(t, 3, len(lbls))
   148  		require.Equal(t, 1, len(chks))
   149  		require.Equal(t, int64(0), chks[0].MinTime)
   150  		require.Equal(t, int64(9), chks[0].MaxTime)
   151  	}
   152  }
   153  
   154  func Test_rowRangeIter(t *testing.T) {
   155  	for _, tc := range []struct {
   156  		name     string
   157  		r        rowRange
   158  		expected []int64
   159  	}{
   160  		{"empty", rowRange{}, []int64{}},
   161  		{"first-element", rowRange{0, 1}, []int64{0}},
   162  		{"first-3-elements", rowRange{0, 3}, []int64{0, 1, 2}},
   163  		{"empty-offset", rowRange{10, 0}, []int64{}},
   164  		{"one-element-offset", rowRange{10, 1}, []int64{10}},
   165  		{"two elements-offset", rowRange{10, 2}, []int64{10, 11}},
   166  	} {
   167  		t.Run(tc.name, func(t *testing.T) {
   168  			it := rowRanges{tc.r: 0xff}.iter()
   169  			result := []int64{}
   170  			for it.Next() {
   171  				result = append(result, it.At().RowNumber())
   172  				assert.Equal(t, model.Fingerprint(0xff), it.At().fp)
   173  			}
   174  			assert.Equal(t, tc.expected, result)
   175  		})
   176  	}
   177  }
   178  
   179  func Test_rowRangesIter(t *testing.T) {
   180  	for _, tc := range []struct {
   181  		name            string
   182  		r               rowRanges
   183  		expRows         []int64
   184  		expFingerprints []model.Fingerprint
   185  	}{
   186  		{name: "empty"},
   187  		{
   188  			name: "empty-with-empty-elements",
   189  			r: rowRanges{
   190  				rowRange{0, 0}:  0xff,
   191  				rowRange{10, 0}: 0xff,
   192  			},
   193  		},
   194  		{
   195  			name: "three-elements-no-gaps",
   196  			r: rowRanges{
   197  				rowRange{1, 3}: 0xfa,
   198  				rowRange{4, 3}: 0xfb,
   199  				rowRange{7, 3}: 0xfc,
   200  			},
   201  			expRows:         []int64{1, 2, 3, 4, 5, 6, 7, 8, 9},
   202  			expFingerprints: []model.Fingerprint{0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc},
   203  		},
   204  		{
   205  			name: "starting-form-zero",
   206  			r: rowRanges{
   207  				rowRange{0, 3}: 0xf0,
   208  			},
   209  			expRows:         []int64{0, 1, 2},
   210  			expFingerprints: []model.Fingerprint{0xf0, 0xf0, 0xf0},
   211  		},
   212  		{
   213  			name: "two-with-gaps",
   214  			r: rowRanges{
   215  				rowRange{1, 3}: 0xfa,
   216  				rowRange{5, 0}: 0xfb,
   217  				rowRange{7, 3}: 0xfc,
   218  			},
   219  			expRows:         []int64{1, 2, 3, 7, 8, 9},
   220  			expFingerprints: []model.Fingerprint{0xfa, 0xfa, 0xfa, 0xfc, 0xfc, 0xfc},
   221  		},
   222  		{
   223  			name: "two-with-0-length-in-between",
   224  			r: rowRanges{
   225  				rowRange{1, 3}: 0xfa,
   226  				rowRange{4, 0}: 0xfb,
   227  				rowRange{7, 3}: 0xfc,
   228  			},
   229  			expRows:         []int64{1, 2, 3, 7, 8, 9},
   230  			expFingerprints: []model.Fingerprint{0xfa, 0xfa, 0xfa, 0xfc, 0xfc, 0xfc},
   231  		},
   232  	} {
   233  		t.Run(tc.name, func(t *testing.T) {
   234  			it := tc.r.iter()
   235  			var (
   236  				rows         []int64
   237  				fingerprints []model.Fingerprint
   238  			)
   239  
   240  			for it.Next() {
   241  				rows = append(rows, it.At().RowNumber())
   242  				fingerprints = append(fingerprints, it.At().fp)
   243  			}
   244  			assert.Equal(t, tc.expRows, rows)
   245  			assert.Equal(t, tc.expFingerprints, fingerprints)
   246  		})
   247  	}
   248  }
   249  
   250  func TestProfileIndex_Add_OutOfOrder(t *testing.T) {
   251  	head := newTestHead(t)
   252  	ctx := context.Background()
   253  
   254  	for idx, ts := range []int64{100, 80, 20, 50, 110} {
   255  		p := testhelper.NewProfileBuilder(ts*1e9).
   256  			CPUProfile().
   257  			WithLabels(
   258  				"job", "a",
   259  			).ForStacktraceString("foo", "bar", "baz", fmt.Sprintf("iteration%d", idx)).AddSamples(1)
   260  
   261  		require.NoError(t, head.Ingest(ctx, p.Profile, uuid.New(), nil))
   262  	}
   263  
   264  	index := head.profiles.index
   265  
   266  	// check that the profiles are in the correct order
   267  	var tsOrder []int64
   268  
   269  	require.Len(t, index.profilesPerFP, 1)
   270  	for _, profiles := range index.profilesPerFP {
   271  		for _, p := range profiles.profiles {
   272  			tsOrder = append(tsOrder, p.TimeNanos/1e9)
   273  		}
   274  
   275  		// check if min/max time is correct
   276  		require.Equal(t, int64(20e9), profiles.minTime)
   277  		require.Equal(t, int64(110e9), profiles.maxTime)
   278  	}
   279  	require.Equal(t, []int64{20, 50, 80, 100, 110}, tsOrder)
   280  
   281  }
   282  
   283  func Test_rowRangesWithSeriesIndex_getSeriesIndex(t *testing.T) {
   284  	testCases := []struct {
   285  		name              string
   286  		ranges            rowRangesWithSeriesIndex
   287  		rowNum            int64
   288  		searchHint        int
   289  		expectSeriesIndex uint32
   290  		expectPanic       bool
   291  	}{
   292  		{
   293  			name: "hit 1",
   294  			ranges: rowRangesWithSeriesIndex{
   295  				{rowRange: &rowRange{rowNum: 0, length: 5}, seriesIndex: 1},
   296  				{rowRange: &rowRange{rowNum: 5, length: 5}, seriesIndex: 2},
   297  			},
   298  			rowNum:            4,
   299  			searchHint:        0,
   300  			expectSeriesIndex: 1,
   301  		},
   302  		{
   303  			name: "hit 2",
   304  			ranges: rowRangesWithSeriesIndex{
   305  				{rowRange: &rowRange{rowNum: 0, length: 5}, seriesIndex: 1},
   306  				{rowRange: &rowRange{rowNum: 5, length: 5}, seriesIndex: 2},
   307  			},
   308  			rowNum:            6,
   309  			searchHint:        1,
   310  			expectSeriesIndex: 2,
   311  		},
   312  		{
   313  			name: "nil rowRange skipped",
   314  			ranges: rowRangesWithSeriesIndex{
   315  				{rowRange: nil, seriesIndex: 1},
   316  				{rowRange: &rowRange{rowNum: 10, length: 5}, seriesIndex: 2},
   317  			},
   318  			rowNum:            12,
   319  			searchHint:        0,
   320  			expectSeriesIndex: 2,
   321  		},
   322  		{
   323  			name:        "not found panics",
   324  			ranges:      rowRangesWithSeriesIndex{{rowRange: &rowRange{rowNum: 0, length: 2}, seriesIndex: 1}},
   325  			rowNum:      10,
   326  			searchHint:  0,
   327  			expectPanic: true,
   328  		},
   329  	}
   330  
   331  	for _, tc := range testCases {
   332  		t.Run(tc.name, func(t *testing.T) {
   333  			searchHint := tc.searchHint
   334  			if tc.expectPanic {
   335  				assert.Panics(t, func() {
   336  					_ = tc.ranges.getSeriesIndex(tc.rowNum, &searchHint)
   337  				})
   338  			} else {
   339  				idx := tc.ranges.getSeriesIndex(tc.rowNum, &searchHint)
   340  				assert.Equal(t, tc.expectSeriesIndex, idx)
   341  			}
   342  		})
   343  	}
   344  }