github.com/grafana/pyroscope@v1.18.0/pkg/metrics/observer_test.go (about)

     1  package metrics
     2  
     3  import (
     4  	"reflect"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/parquet-go/parquet-go"
     9  	"github.com/prometheus/common/model"
    10  	"github.com/prometheus/prometheus/model/labels"
    11  	"github.com/prometheus/prometheus/prompb"
    12  	"github.com/stretchr/testify/mock"
    13  
    14  	metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1"
    15  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    16  	"github.com/grafana/pyroscope/pkg/block"
    17  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    18  	v1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1"
    19  	"github.com/grafana/pyroscope/pkg/test/mocks/mockmetrics"
    20  )
    21  
    22  var (
    23  	blockTime   = int64(0)
    24  	profileTime = int64(1)
    25  )
    26  
    27  func Test_Observer_observe(t *testing.T) {
    28  	exporter := new(mockmetrics.MockExporter)
    29  	ruler := new(mockmetrics.MockRuler)
    30  	ruler.On("RecordingRules", mock.Anything).Return([]*phlaremodel.RecordingRule{
    31  		{
    32  			Matchers: []*labels.Matcher{
    33  				labels.MustNewMatcher(labels.MatchEqual, "a", "1"),
    34  				labels.MustNewMatcher(labels.MatchEqual, "b", "1"),
    35  			},
    36  			GroupBy:        []string{"c"},
    37  			ExternalLabels: labels.New(labels.Label{Name: "external1", Value: "external1"}),
    38  		},
    39  		{
    40  			Matchers: []*labels.Matcher{
    41  				labels.MustNewMatcher(labels.MatchEqual, "d", "1"),
    42  			},
    43  			GroupBy: []string{},
    44  		},
    45  	})
    46  	observer := NewSampleObserver(blockTime, exporter, ruler, labels.New(labels.Label{Name: "external2", Value: "external2"}))
    47  	entries := entriesOf([][]any{
    48  		{"tenant1", [][]string{{"a", "0"}, {"b", "0"}, {"c", "0"}, {"d", "0"}}, int64(1) << 0},
    49  		{"tenant1", [][]string{{"a", "0"}, {"b", "0"}, {"c", "0"}, {"d", "1"}}, int64(1) << 1},
    50  		{"tenant1", [][]string{{"a", "0"}, {"b", "0"}, {"c", "1"}, {"d", "0"}}, int64(1) << 2},
    51  		{"tenant1", [][]string{{"a", "0"}, {"b", "0"}, {"c", "1"}, {"d", "1"}}, int64(1) << 3},
    52  		{"tenant1", [][]string{{"a", "0"}, {"b", "1"}, {"c", "0"}, {"d", "0"}}, int64(1) << 4},
    53  		{"tenant1", [][]string{{"a", "0"}, {"b", "1"}, {"c", "0"}, {"d", "1"}}, int64(1) << 5},
    54  		{"tenant1", [][]string{{"a", "0"}, {"b", "1"}, {"c", "1"}, {"d", "0"}}, int64(1) << 6},
    55  		{"tenant1", [][]string{{"a", "0"}, {"b", "1"}, {"c", "1"}, {"d", "1"}}, int64(1) << 7},
    56  		{"tenant1", [][]string{{"a", "1"}, {"b", "0"}, {"c", "0"}, {"d", "0"}}, int64(1) << 8},
    57  		{"tenant1", [][]string{{"a", "1"}, {"b", "0"}, {"c", "0"}, {"d", "1"}}, int64(1) << 9},
    58  		{"tenant1", [][]string{{"a", "1"}, {"b", "0"}, {"c", "1"}, {"d", "0"}}, int64(1) << 10},
    59  		{"tenant1", [][]string{{"a", "1"}, {"b", "0"}, {"c", "1"}, {"d", "1"}}, int64(1) << 11},
    60  		{"tenant1", [][]string{{"a", "1"}, {"b", "1"}, {"c", "0"}, {"d", "0"}}, int64(1) << 12},
    61  		{"tenant1", [][]string{{"a", "1"}, {"b", "1"}, {"c", "0"}, {"d", "1"}}, int64(1) << 13},
    62  		{"tenant1", [][]string{{"a", "1"}, {"b", "1"}, {"c", "1"}, {"d", "0"}}, int64(1) << 14},
    63  		{"tenant1", [][]string{{"a", "1"}, {"b", "1"}, {"c", "1"}, {"d", "1"}}, int64(1) << 15},
    64  		{"tenant2", [][]string{{"x", "1"}}, int64(1)},
    65  		{"tenant3", [][]string{{"a", "1"}, {"b", "1"}, {"c", "1"}, {"d", "1"}}, int64(1) << 0},
    66  		{"tenant3", [][]string{{"a", "1"}, {"b", "1"}, {"c", "1"}, {"d", "1"}}, int64(1) << 1},
    67  		{"tenant3", [][]string{{"a", "1"}, {"b", "1"}, {"c", "1"}, {"d", "1"}}, int64(1) << 2},
    68  	})
    69  
    70  	exporter.On("Send", "tenant1",
    71  		mock.MatchedBy(func(series []prompb.TimeSeries) bool {
    72  			return sameSeries(series, []prompb.TimeSeries{
    73  				timeSeriesOf([]any{[][]string{{"c", "0"}, {"external1", "external1"}, {"external2", "external2"}}, 1<<12 + 1<<13, blockTime}),
    74  				timeSeriesOf([]any{[][]string{{"c", "1"}, {"external1", "external1"}, {"external2", "external2"}}, 1<<14 + 1<<15, blockTime}),
    75  				timeSeriesOf([]any{[][]string{{"external2", "external2"}}, 1<<1 + 1<<3 + 1<<5 + 1<<7 + 1<<9 + 1<<11 + 1<<13 + 1<<15, blockTime}),
    76  			})
    77  		}),
    78  	).Return(nil).Once()
    79  
    80  	exporter.On("Send", "tenant3",
    81  		mock.MatchedBy(func(series []prompb.TimeSeries) bool {
    82  			return sameSeries(series, []prompb.TimeSeries{
    83  				timeSeriesOf([]any{[][]string{{"c", "1"}, {"external1", "external1"}, {"external2", "external2"}}, 1<<0 + 1<<1 + 1<<2, blockTime}),
    84  				timeSeriesOf([]any{[][]string{{"external2", "external2"}}, 1<<0 + 1<<1 + 1<<2, blockTime}),
    85  			})
    86  		}),
    87  	).Return(nil).Once()
    88  
    89  	for _, entry := range entries {
    90  		observe := observer.Evaluate(entry)
    91  		// TODO(alsoba13): Test observeSymbols
    92  		observe()
    93  	}
    94  	observer.Close()
    95  
    96  	ruler.AssertExpectations(t)
    97  	exporter.AssertExpectations(t)
    98  	exporter.AssertNotCalled(t, "Send", "tenant2", mock.Anything)
    99  }
   100  
   101  func sameSeries(series1 []prompb.TimeSeries, series2 []prompb.TimeSeries) bool {
   102  	for _, s := range series1 {
   103  		found := false
   104  		for _, s2 := range series2 {
   105  			found = reflect.DeepEqual(s, s2)
   106  			if found {
   107  				break
   108  			}
   109  		}
   110  		if !found {
   111  			return false
   112  		}
   113  	}
   114  	return true
   115  }
   116  
   117  func timeSeriesOf(values []any) prompb.TimeSeries {
   118  	lbls := make([]prompb.Label, len(values[0].([][]string)))
   119  	for i, label := range values[0].([][]string) {
   120  		lbls[i] = prompb.Label{
   121  			Name:  label[0],
   122  			Value: label[1],
   123  		}
   124  	}
   125  	return prompb.TimeSeries{
   126  		Labels: lbls,
   127  		Samples: []prompb.Sample{
   128  			{
   129  				Value:     float64(values[1].(int)),
   130  				Timestamp: values[2].(int64),
   131  			},
   132  		},
   133  	}
   134  }
   135  
   136  func entriesOf(values [][]any) []block.ProfileEntry {
   137  	profileEntries := make([]block.ProfileEntry, len(values))
   138  	for i, value := range values {
   139  		ls := make(phlaremodel.Labels, len(value[1].([][]string)))
   140  		for j, label := range value[1].([][]string) {
   141  			ls[j] = &typesv1.LabelPair{
   142  				Name:  label[0],
   143  				Value: label[1],
   144  			}
   145  		}
   146  		sort.Sort(ls)
   147  		row := make(v1.ProfileRow, 4)
   148  		row[3] = parquet.Int64Value(value[2].(int64))
   149  		profileEntries[i] = block.ProfileEntry{
   150  			Dataset:     datasetForTenant(value[0].(string)),
   151  			Timestamp:   profileTime,
   152  			Fingerprint: model.Fingerprint(ls.Hash()),
   153  			Labels:      ls,
   154  			Row:         row,
   155  		}
   156  	}
   157  	return profileEntries
   158  }
   159  
   160  func datasetForTenant(tenant string) *block.Dataset {
   161  	return block.NewDataset(
   162  		&metastorev1.Dataset{},
   163  		block.NewObject(
   164  			nil,
   165  			&metastorev1.BlockMeta{StringTable: []string{tenant}},
   166  		),
   167  	)
   168  }