Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0b6c47c

Browse files
committedFeb 20, 2025·
fastdto attempt.
Signed-off-by: bwplotka <[email protected]>
1 parent 6da4db6 commit 0b6c47c

File tree

10 files changed

+158
-39
lines changed

10 files changed

+158
-39
lines changed
 

‎prometheus/counter.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"sync/atomic"
2020
"time"
2121

22+
"github.com/prometheus/client_golang/prometheus/internal/fastdto"
2223
dto "github.com/prometheus/client_model/go"
2324
"google.golang.org/protobuf/types/known/timestamppb"
2425
)
@@ -94,7 +95,7 @@ func NewCounter(opts CounterOpts) Counter {
9495
if opts.now == nil {
9596
opts.now = time.Now
9697
}
97-
result := &counter{desc: desc, labelPairs: desc.labelPairs, now: opts.now}
98+
result := &counter{desc: desc, labelPairs: fastdto.ToDTOLabelPair(desc.labelPairs), now: opts.now}
9899
result.init(result) // Init self-collection.
99100
result.createdTs = timestamppb.New(opts.now())
100101
return result

‎prometheus/desc.go

+12-17
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,8 @@ import (
1919
"strings"
2020

2121
"github.com/cespare/xxhash/v2"
22-
dto "github.com/prometheus/client_model/go"
22+
"github.com/prometheus/client_golang/prometheus/internal/fastdto"
2323
"github.com/prometheus/common/model"
24-
"google.golang.org/protobuf/proto"
25-
26-
"github.com/prometheus/client_golang/prometheus/internal"
2724
)
2825

2926
// Desc is the descriptor used by every Prometheus Metric. It is essentially
@@ -56,7 +53,7 @@ type Desc struct {
5653
variableLabelOrder []int
5754
// labelPairs contains the sorted DTO label pairs based on the constant labels
5855
// and variable labels
59-
labelPairs []*dto.LabelPair
56+
labelPairs []fastdto.LabelPair
6057
// id is a hash of the values of the ConstLabels and fqName. This
6158
// must be unique among all registered descriptors and can therefore be
6259
// used as an identifier of the descriptor.
@@ -164,24 +161,23 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
164161
}
165162
d.dimHash = xxh.Sum64()
166163

167-
d.labelPairs = make([]*dto.LabelPair, 0, len(constLabels)+len(d.variableLabels.names))
164+
d.labelPairs = make([]fastdto.LabelPair, len(constLabels)+len(d.variableLabels.names))
165+
i := 0
168166
for n, v := range constLabels {
169-
d.labelPairs = append(d.labelPairs, &dto.LabelPair{
170-
Name: proto.String(n),
171-
Value: proto.String(v),
172-
})
167+
d.labelPairs[i].Name = n
168+
d.labelPairs[i].Value = v
169+
i++
173170
}
174171
for _, labelName := range d.variableLabels.names {
175-
d.labelPairs = append(d.labelPairs, &dto.LabelPair{
176-
Name: proto.String(labelName),
177-
})
172+
d.labelPairs[i].Name = labelName
173+
i++
178174
}
179-
sort.Sort(internal.LabelPairSorter(d.labelPairs))
175+
sort.Sort(fastdto.LabelPairSorter(d.labelPairs))
180176

181177
d.variableLabelOrder = make([]int, len(d.variableLabels.names))
182178
for outputIndex, pair := range d.labelPairs {
183179
// Constant labels have values variable labels do not.
184-
if pair.Value != nil {
180+
if pair.Value != "" {
185181
continue
186182
}
187183
for sourceIndex, variableLabel := range d.variableLabels.names {
@@ -190,7 +186,6 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
190186
}
191187
}
192188
}
193-
194189
return d
195190
}
196191

@@ -207,7 +202,7 @@ func NewInvalidDesc(err error) *Desc {
207202
func (d *Desc) String() string {
208203
lpStrings := make([]string, 0, len(d.labelPairs))
209204
for _, lp := range d.labelPairs {
210-
if lp.Value == nil {
205+
if lp.Value == "" {
211206
continue
212207
}
213208
lpStrings = append(

‎prometheus/desc_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ func TestNewInvalidDesc_String(t *testing.T) {
6363
}
6464
}
6565

66+
/*
67+
export bench=newDesc && go test ./prometheus \
68+
-run '^$' -bench '^BenchmarkNewDesc/labels=10' \
69+
-benchtime 5s -benchmem -cpu 2 -timeout 999m \
70+
-memprofile=${bench}.mem.pprof \
71+
| tee ${bench}.txt
72+
73+
export bench=newDesc-v2 && go test ./prometheus \
74+
-run '^$' -bench '^BenchmarkNewDesc' \
75+
-benchtime 5s -benchmem -count=6 -cpu 2 -timeout 999m \
76+
| tee ${bench}.txt
77+
*/
6678
func BenchmarkNewDesc(b *testing.B) {
6779
for _, bm := range []struct {
6880
labelCount int
@@ -82,6 +94,8 @@ func BenchmarkNewDesc(b *testing.B) {
8294
},
8395
} {
8496
b.Run(fmt.Sprintf("labels=%v", bm.labelCount), func(b *testing.B) {
97+
b.ReportAllocs()
98+
b.ResetTimer()
8599
for i := 0; i < b.N; i++ {
86100
bm.descFunc()
87101
}

‎prometheus/gauge.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"sync/atomic"
1919
"time"
2020

21+
"github.com/prometheus/client_golang/prometheus/internal/fastdto"
2122
dto "github.com/prometheus/client_model/go"
2223
)
2324

@@ -82,7 +83,7 @@ func NewGauge(opts GaugeOpts) Gauge {
8283
nil,
8384
opts.ConstLabels,
8485
)
85-
result := &gauge{desc: desc, labelPairs: desc.labelPairs}
86+
result := &gauge{desc: desc, labelPairs: fastdto.ToDTOLabelPair(desc.labelPairs)}
8687
result.init(result) // Init self-collection.
8788
return result
8889
}

‎prometheus/internal/fastdto/labels.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package fastdto
2+
3+
import (
4+
dto "github.com/prometheus/client_model/go"
5+
)
6+
7+
type LabelPair struct {
8+
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
9+
Value string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
10+
}
11+
12+
func (p LabelPair) GetName() string {
13+
return p.Name
14+
}
15+
16+
func (p LabelPair) GetValue() string {
17+
return p.Value
18+
}
19+
20+
// LabelPairSorter implements sort.Interface. It is used to sort a slice of
21+
// LabelPairs
22+
type LabelPairSorter []LabelPair
23+
24+
func (s LabelPairSorter) Len() int {
25+
return len(s)
26+
}
27+
28+
func (s LabelPairSorter) Swap(i, j int) {
29+
s[i], s[j] = s[j], s[i]
30+
}
31+
32+
func (s LabelPairSorter) Less(i, j int) bool {
33+
return s[i].Name < s[j].Name
34+
}
35+
36+
func ToDTOLabelPair(in []LabelPair) []*dto.LabelPair {
37+
ret := make([]*dto.LabelPair, len(in))
38+
for i := range in {
39+
ret[i] = &dto.LabelPair{
40+
Name: &(in[i].Name),
41+
Value: &(in[i].Value),
42+
}
43+
}
44+
return ret
45+
}
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package fastdto
2+
3+
import (
4+
"sort"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-cmp/cmp/cmpopts"
9+
dto "github.com/prometheus/client_model/go"
10+
"google.golang.org/protobuf/proto"
11+
)
12+
13+
func BenchmarkToDTOLabelPairs(b *testing.B) {
14+
test := []LabelPair{
15+
{"foo", "bar"},
16+
{"foo2", "bar2"},
17+
{"foo3", "bar3"},
18+
}
19+
b.ReportAllocs()
20+
b.ResetTimer()
21+
for i := 0; i < b.N; i++ {
22+
_ = ToDTOLabelPair(test)
23+
}
24+
}
25+
26+
func TestLabelPairSorter(t *testing.T) {
27+
test := []LabelPair{
28+
{"foo3", "bar3"},
29+
{"foo", "bar"},
30+
{"foo2", "bar2"},
31+
}
32+
sort.Sort(LabelPairSorter(test))
33+
34+
expected := []LabelPair{
35+
{"foo", "bar"},
36+
{"foo2", "bar2"},
37+
{"foo3", "bar3"},
38+
}
39+
if diff := cmp.Diff(test, expected); diff != "" {
40+
t.Fatal(diff)
41+
}
42+
}
43+
44+
func TestToDTOLabelPair(t *testing.T) {
45+
test := []LabelPair{
46+
{"foo", "bar"},
47+
{"foo2", "bar2"},
48+
{"foo3", "bar3"},
49+
}
50+
expected := []*dto.LabelPair{
51+
{Name: proto.String("foo"), Value: proto.String("bar")},
52+
{Name: proto.String("foo2"), Value: proto.String("bar2")},
53+
{Name: proto.String("foo3"), Value: proto.String("bar3")},
54+
}
55+
if diff := cmp.Diff(ToDTOLabelPair(test), expected, cmpopts.IgnoreUnexported(dto.LabelPair{})); diff != "" {
56+
t.Fatal(diff)
57+
}
58+
}

‎prometheus/registry.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@ func checkDescConsistency(
971971
for i, lpFromDesc := range desc.labelPairs {
972972
lpFromMetric := dtoMetric.Label[i]
973973
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
974-
lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
974+
lpFromDesc.Value != "" && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
975975
return fmt.Errorf(
976976
"labels in collected metric %s %s are inconsistent with descriptor %s",
977977
metricFamily.GetName(), dtoMetric, desc,

‎prometheus/value.go

+8-17
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"time"
2020
"unicode/utf8"
2121

22+
"github.com/prometheus/client_golang/prometheus/internal/fastdto"
2223
dto "github.com/prometheus/client_model/go"
2324
"google.golang.org/protobuf/proto"
2425
"google.golang.org/protobuf/types/known/timestamppb"
@@ -216,28 +217,18 @@ func MakeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
216217
// Super fast path.
217218
return nil
218219
}
220+
221+
// Conversion copies all but strings.
222+
ret := fastdto.ToDTOLabelPair(desc.labelPairs)
219223
if len(desc.variableLabels.names) == 0 {
220224
// Moderately fast path.
221-
return desc.labelPairs
222-
}
223-
labelPairs := make([]*dto.LabelPair, 0, len(desc.labelPairs))
224-
for _, lp := range desc.labelPairs {
225-
var labelToAdd *dto.LabelPair
226-
// Variable labels have no value and need to be inserted with a new dto.LabelPair containing the labelValue.
227-
if lp.Value == nil {
228-
labelToAdd = &dto.LabelPair{
229-
Name: lp.Name,
230-
}
231-
} else {
232-
labelToAdd = lp
233-
}
234-
labelPairs = append(labelPairs, labelToAdd)
225+
return ret
235226
}
227+
236228
for i, outputIndex := range desc.variableLabelOrder {
237-
labelPairs[outputIndex].Value = proto.String(labelValues[i])
229+
ret[outputIndex].Value = &labelValues[i] // Reusing string, assuming it's safe.
238230
}
239-
240-
return labelPairs
231+
return ret
241232
}
242233

243234
// ExemplarMaxRunes is the max total number of runes allowed in exemplar labels.

‎prometheus/value_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,18 @@ var new10LabelsDescFunc = func() *Desc {
217217
Labels{"const-label-4": "value", "const-label-1": "value", "const-label-7": "value", "const-label-2": "value", "const-label-9": "value", "const-label-8": "value", "const-label-10": "value", "const-label-3": "value", "const-label-6": "value", "const-label-5": "value"})
218218
}
219219

220+
/*
221+
export bench=makeLabelPairs-v2 && go test ./prometheus \
222+
-run '^$' -bench '^BenchmarkMakeLabelPairs' \
223+
-benchtime 5s -benchmem -count=6 -cpu 2 -timeout 999m \
224+
| tee ${bench}.txt
225+
226+
export bench=makeLabelPairs-v2pp && go test ./prometheus \
227+
-run '^$' -bench '^BenchmarkMakeLabelPairs' \
228+
-benchtime 5s -benchmem -cpu 2 -timeout 999m \
229+
-memprofile=${bench}.mem.pprof \
230+
| tee ${bench}.txt
231+
*/
220232
func BenchmarkMakeLabelPairs(b *testing.B) {
221233
for _, bm := range []struct {
222234
desc *Desc
@@ -236,6 +248,8 @@ func BenchmarkMakeLabelPairs(b *testing.B) {
236248
},
237249
} {
238250
b.Run(fmt.Sprintf("labels=%v", len(bm.makeLabelPairValues)), func(b *testing.B) {
251+
b.ReportAllocs()
252+
b.ResetTimer()
239253
for i := 0; i < b.N; i++ {
240254
MakeLabelPairs(bm.desc, bm.makeLabelPairValues)
241255
}

‎prometheus/wrap.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc {
190190
constLabels := Labels{}
191191
for _, lp := range desc.labelPairs {
192192
// Variable labels have no values
193-
if lp.Value != nil {
194-
constLabels[*lp.Name] = *lp.Value
193+
if lp.Value == "" {
194+
constLabels[lp.Name] = lp.Value
195195
}
196196
}
197197
for ln, lv := range labels {

0 commit comments

Comments
 (0)
Please sign in to comment.