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 }