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 }