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 }