Skip to content

Commit 5eda406

Browse files
authored
fix: group by with enforced labels order (#3352)
1 parent efc7f2a commit 5eda406

File tree

3 files changed

+65
-117
lines changed

3 files changed

+65
-117
lines changed

pkg/distributor/distributor.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -419,9 +419,7 @@ func (d *Distributor) sendRequests(ctx context.Context, req *distributormodel.Pu
419419
enforceLabelsOrder := d.limits.EnforceLabelsOrder(tenantID)
420420
for i, series := range profileSeries {
421421
if enforceLabelsOrder {
422-
labels := phlaremodel.Labels(series.Labels)
423-
labels.Insert(phlaremodel.LabelNameOrder, phlaremodel.LabelOrderEnforced)
424-
series.Labels = labels
422+
series.Labels = phlaremodel.Labels(series.Labels).InsertSorted(phlaremodel.LabelNameOrder, phlaremodel.LabelOrderEnforced)
425423
}
426424
if err = validation.ValidateLabels(d.limits, tenantID, series.Labels); err != nil {
427425
validation.DiscardedProfiles.WithLabelValues(string(validation.ReasonOf(err)), tenantID).Add(float64(req.TotalProfiles))

pkg/model/labels.go

+42-112
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@ import (
55
"encoding/binary"
66
"encoding/hex"
77
"fmt"
8+
"slices"
89
"sort"
910
"strconv"
1011
"strings"
1112

1213
"github.com/cespare/xxhash/v2"
1314
pmodel "github.com/prometheus/common/model"
1415
"github.com/prometheus/prometheus/model/labels"
15-
"github.com/prometheus/prometheus/promql/parser"
1616

1717
typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
18-
"github.com/grafana/pyroscope/pkg/slices"
1918
"github.com/grafana/pyroscope/pkg/util"
2019
)
2120

@@ -102,52 +101,25 @@ func (ls Labels) Hash() uint64 {
102101
return xxhash.Sum64(b)
103102
}
104103

105-
// HashForLabels returns a hash value for the labels matching the provided names.
106-
// 'names' have to be sorted in ascending order.
107-
func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) {
108-
b = b[:0]
109-
i, j := 0, 0
110-
for i < len(ls) && j < len(names) {
111-
if names[j] < ls[i].Name {
112-
j++
113-
} else if ls[i].Name < names[j] {
114-
i++
115-
} else {
116-
b = append(b, ls[i].Name...)
117-
b = append(b, seps[0])
118-
b = append(b, ls[i].Value...)
119-
b = append(b, seps[0])
120-
i++
121-
j++
122-
}
123-
}
124-
return xxhash.Sum64(b), b
125-
}
126-
127104
// BytesWithLabels is just as Bytes(), but only for labels matching names.
128-
// 'names' have to be sorted in ascending order.
129105
// It uses an byte invalid character as a separator and so should not be used for printing.
130106
func (ls Labels) BytesWithLabels(buf []byte, names ...string) []byte {
131-
b := bytes.NewBuffer(buf[:0])
132-
b.WriteByte(labelSep)
133-
i, j := 0, 0
134-
for i < len(ls) && j < len(names) {
135-
if names[j] < ls[i].Name {
136-
j++
137-
} else if ls[i].Name < names[j] {
138-
i++
139-
} else {
140-
if b.Len() > 1 {
141-
b.WriteByte(seps[0])
107+
buf = buf[:0]
108+
buf = append(buf, labelSep)
109+
for _, name := range names {
110+
for _, l := range ls {
111+
if l.Name == name {
112+
if len(buf) > 1 {
113+
buf = append(buf, seps[0])
114+
}
115+
buf = append(buf, l.Name...)
116+
buf = append(buf, seps[0])
117+
buf = append(buf, l.Value...)
118+
break
142119
}
143-
b.WriteString(ls[i].Name)
144-
b.WriteByte(seps[0])
145-
b.WriteString(ls[i].Value)
146-
i++
147-
j++
148120
}
149121
}
150-
return b.Bytes()
122+
return buf
151123
}
152124

153125
func (ls Labels) ToPrometheusLabels() labels.Labels {
@@ -180,22 +152,18 @@ func IsLabelAllowedForIngestion(name string) bool {
180152
return allowed
181153
}
182154

183-
// WithLabels returns a subset of Labels that matches match with the provided label names.
155+
// WithLabels returns a subset of Labels that match with the provided label names.
184156
func (ls Labels) WithLabels(names ...string) Labels {
185-
matchedLabels := Labels{}
186-
187-
nameSet := make(map[string]struct{}, len(names))
188-
for _, n := range names {
189-
nameSet[n] = struct{}{}
190-
}
191-
192-
for _, v := range ls {
193-
if _, ok := nameSet[v.Name]; ok {
194-
matchedLabels = append(matchedLabels, v)
157+
matched := make(Labels, 0, len(names))
158+
for _, name := range names {
159+
for _, l := range ls {
160+
if l.Name == name {
161+
matched = append(matched, l)
162+
break
163+
}
195164
}
196165
}
197-
198-
return matchedLabels
166+
return matched
199167
}
200168

201169
// Get returns the value for the label with the given name.
@@ -221,37 +189,42 @@ func (ls Labels) GetLabel(name string) (*typesv1.LabelPair, bool) {
221189

222190
// Delete removes the first label encountered with the name given in place.
223191
func (ls Labels) Delete(name string) Labels {
224-
return slices.RemoveInPlace(ls, func(pair *typesv1.LabelPair, i int) bool {
225-
return pair.Name == name
226-
})
192+
for i, l := range ls {
193+
if l.Name == name {
194+
return slices.Delete(ls, i, i+1)
195+
}
196+
}
197+
return ls
227198
}
228199

229-
// Insert adds the given label to the set of labels.
230-
// It assumes the labels are ordered
231-
func (ls *Labels) Insert(name, value string) {
232-
// Find the index where the new label should be inserted
200+
// InsertSorted adds the given label to the set of labels.
201+
// It assumes the labels are sorted lexicographically.
202+
func (ls Labels) InsertSorted(name, value string) Labels {
203+
// Find the index where the new label should be inserted.
204+
// TODO: Use binary search on large label sets.
233205
index := -1
234-
for i, label := range *ls {
206+
for i, label := range ls {
235207
if label.Name > name {
236208
index = i
237209
break
238210
}
239211
if label.Name == name {
240212
label.Value = value
241-
return
213+
return ls
242214
}
243215
}
244-
// Insert the new label at the found index
216+
// Insert the new label at the found index.
245217
l := &typesv1.LabelPair{
246218
Name: name,
247219
Value: value,
248220
}
249-
*ls = append(*ls, l)
221+
c := append(ls, l)
250222
if index == -1 {
251-
return
223+
return c
252224
}
253-
copy((*ls)[index+1:], (*ls)[index:])
254-
(*ls)[index] = l
225+
copy((c)[index+1:], (c)[index:])
226+
(c)[index] = l
227+
return c
255228
}
256229

257230
func (ls Labels) Clone() Labels {
@@ -301,22 +274,6 @@ func LabelPairsString(lbs []*typesv1.LabelPair) string {
301274
return b.String()
302275
}
303276

304-
// StringToLabelsPairs converts a string representation of label pairs to a slice of label pairs.
305-
func StringToLabelsPairs(s string) ([]*typesv1.LabelPair, error) {
306-
matchers, err := parser.ParseMetricSelector(s)
307-
if err != nil {
308-
return nil, err
309-
}
310-
result := make([]*typesv1.LabelPair, len(matchers))
311-
for i := range matchers {
312-
result[i] = &typesv1.LabelPair{
313-
Name: matchers[i].Name,
314-
Value: matchers[i].Value,
315-
}
316-
}
317-
return result, nil
318-
}
319-
320277
// LabelsFromStrings creates new labels from pairs of strings.
321278
func LabelsFromStrings(ss ...string) Labels {
322279
if len(ss)%2 != 0 {
@@ -454,33 +411,6 @@ Outer:
454411
return append(res, b.add...)
455412
}
456413

457-
// StableHash is a labels hashing implementation which is guaranteed to not change over time.
458-
// This function should be used whenever labels hashing backward compatibility must be guaranteed.
459-
func StableHash(ls labels.Labels) uint64 {
460-
// Use xxhash.Sum64(b) for fast path as it's faster.
461-
b := make([]byte, 0, 1024)
462-
for i, v := range ls {
463-
if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) {
464-
// If labels entry is 1KB+ do not allocate whole entry.
465-
h := xxhash.New()
466-
_, _ = h.Write(b)
467-
for _, v := range ls[i:] {
468-
_, _ = h.WriteString(v.Name)
469-
_, _ = h.Write(seps)
470-
_, _ = h.WriteString(v.Value)
471-
_, _ = h.Write(seps)
472-
}
473-
return h.Sum64()
474-
}
475-
476-
b = append(b, v.Name...)
477-
b = append(b, seps[0])
478-
b = append(b, v.Value...)
479-
b = append(b, seps[0])
480-
}
481-
return xxhash.Sum64(b)
482-
}
483-
484414
type SessionID uint64
485415

486416
func (s SessionID) String() string {

pkg/model/labels_test.go

+22-2
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,27 @@ func TestLabels_LabelsEnforcedOrder(t *testing.T) {
158158
})
159159
}
160160

161+
func TestLabels_LabelsEnforcedOrder_BytesWithLabels(t *testing.T) {
162+
labels := Labels{
163+
{Name: LabelNameProfileType, Value: "cpu"},
164+
{Name: LabelNameServiceNamePrivate, Value: "service"},
165+
{Name: "__request_id__", Value: "mess"},
166+
{Name: "A_label", Value: "bad"},
167+
{Name: "foo", Value: "bar"},
168+
}
169+
sort.Sort(LabelsEnforcedOrder(labels))
170+
171+
assert.NotEqual(t,
172+
labels.BytesWithLabels(nil, "A_label"),
173+
labels.BytesWithLabels(nil, "not_a_label"),
174+
)
175+
176+
assert.Equal(t,
177+
labels.BytesWithLabels(nil, "A_label"),
178+
Labels{{Name: "A_label", Value: "bad"}}.BytesWithLabels(nil, "A_label"),
179+
)
180+
}
181+
161182
func permute[T any](s []T, f func([]T)) {
162183
n := len(s)
163184
stack := make([]int, n)
@@ -256,8 +277,7 @@ func TestInsert(t *testing.T) {
256277

257278
for _, test := range tests {
258279
t.Run(test.name, func(t *testing.T) {
259-
test.labels.Insert(test.insertName, test.insertValue)
260-
assert.Equal(t, test.expected, test.labels)
280+
assert.Equal(t, test.expected, test.labels.InsertSorted(test.insertName, test.insertValue))
261281
})
262282
}
263283
}

0 commit comments

Comments
 (0)