Skip to content

Commit 6ada5c2

Browse files
committed
fastdto attempt.
Signed-off-by: bwplotka <[email protected]>
1 parent 6da4db6 commit 6ada5c2

File tree

10 files changed

+188
-39
lines changed

10 files changed

+188
-39
lines changed

prometheus/counter.go

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

22+
"github.com/prometheus/client_golang/prometheus/internal/fastdto"
23+
2224
dto "github.com/prometheus/client_model/go"
2325
"google.golang.org/protobuf/types/known/timestamppb"
2426
)
@@ -94,7 +96,7 @@ func NewCounter(opts CounterOpts) Counter {
9496
if opts.now == nil {
9597
opts.now = time.Now
9698
}
97-
result := &counter{desc: desc, labelPairs: desc.labelPairs, now: opts.now}
99+
result := &counter{desc: desc, labelPairs: fastdto.ToDTOLabelPair(desc.labelPairs), now: opts.now}
98100
result.init(result) // Init self-collection.
99101
result.createdTs = timestamppb.New(opts.now())
100102
return result

prometheus/desc.go

+13-17
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ import (
1818
"sort"
1919
"strings"
2020

21+
"github.com/prometheus/client_golang/prometheus/internal/fastdto"
22+
2123
"github.com/cespare/xxhash/v2"
22-
dto "github.com/prometheus/client_model/go"
2324
"github.com/prometheus/common/model"
24-
"google.golang.org/protobuf/proto"
25-
26-
"github.com/prometheus/client_golang/prometheus/internal"
2725
)
2826

2927
// Desc is the descriptor used by every Prometheus Metric. It is essentially
@@ -56,7 +54,7 @@ type Desc struct {
5654
variableLabelOrder []int
5755
// labelPairs contains the sorted DTO label pairs based on the constant labels
5856
// and variable labels
59-
labelPairs []*dto.LabelPair
57+
labelPairs []fastdto.LabelPair
6058
// id is a hash of the values of the ConstLabels and fqName. This
6159
// must be unique among all registered descriptors and can therefore be
6260
// used as an identifier of the descriptor.
@@ -164,24 +162,23 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
164162
}
165163
d.dimHash = xxh.Sum64()
166164

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

181178
d.variableLabelOrder = make([]int, len(d.variableLabels.names))
182179
for outputIndex, pair := range d.labelPairs {
183180
// Constant labels have values variable labels do not.
184-
if pair.Value != nil {
181+
if pair.Value != "" {
185182
continue
186183
}
187184
for sourceIndex, variableLabel := range d.variableLabels.names {
@@ -190,7 +187,6 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
190187
}
191188
}
192189
}
193-
194190
return d
195191
}
196192

@@ -207,7 +203,7 @@ func NewInvalidDesc(err error) *Desc {
207203
func (d *Desc) String() string {
208204
lpStrings := make([]string, 0, len(d.labelPairs))
209205
for _, lp := range d.labelPairs {
210-
if lp.Value == nil {
206+
if lp.Value == "" {
211207
continue
212208
}
213209
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

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

21+
"github.com/prometheus/client_golang/prometheus/internal/fastdto"
22+
2123
dto "github.com/prometheus/client_model/go"
2224
)
2325

@@ -82,7 +84,7 @@ func NewGauge(opts GaugeOpts) Gauge {
8284
nil,
8385
opts.ConstLabels,
8486
)
85-
result := &gauge{desc: desc, labelPairs: desc.labelPairs}
87+
result := &gauge{desc: desc, labelPairs: fastdto.ToDTOLabelPair(desc.labelPairs)}
8688
result.init(result) // Init self-collection.
8789
return result
8890
}

prometheus/internal/fastdto/labels.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2025 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package fastdto
15+
16+
import (
17+
dto "github.com/prometheus/client_model/go"
18+
)
19+
20+
type LabelPair struct {
21+
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
22+
Value string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
23+
}
24+
25+
func (p LabelPair) GetName() string {
26+
return p.Name
27+
}
28+
29+
func (p LabelPair) GetValue() string {
30+
return p.Value
31+
}
32+
33+
// LabelPairSorter implements sort.Interface. It is used to sort a slice of
34+
// LabelPairs
35+
type LabelPairSorter []LabelPair
36+
37+
func (s LabelPairSorter) Len() int {
38+
return len(s)
39+
}
40+
41+
func (s LabelPairSorter) Swap(i, j int) {
42+
s[i], s[j] = s[j], s[i]
43+
}
44+
45+
func (s LabelPairSorter) Less(i, j int) bool {
46+
return s[i].Name < s[j].Name
47+
}
48+
49+
func ToDTOLabelPair(in []LabelPair) []*dto.LabelPair {
50+
ret := make([]*dto.LabelPair, len(in))
51+
for i := range in {
52+
ret[i] = &dto.LabelPair{
53+
Name: &(in[i].Name),
54+
Value: &(in[i].Value),
55+
}
56+
}
57+
return ret
58+
}
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2025 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package fastdto
15+
16+
import (
17+
"sort"
18+
"testing"
19+
20+
"github.com/google/go-cmp/cmp"
21+
"github.com/google/go-cmp/cmp/cmpopts"
22+
dto "github.com/prometheus/client_model/go"
23+
"google.golang.org/protobuf/proto"
24+
)
25+
26+
func BenchmarkToDTOLabelPairs(b *testing.B) {
27+
test := []LabelPair{
28+
{"foo", "bar"},
29+
{"foo2", "bar2"},
30+
{"foo3", "bar3"},
31+
}
32+
b.ReportAllocs()
33+
b.ResetTimer()
34+
for i := 0; i < b.N; i++ {
35+
_ = ToDTOLabelPair(test)
36+
}
37+
}
38+
39+
func TestLabelPairSorter(t *testing.T) {
40+
test := []LabelPair{
41+
{"foo3", "bar3"},
42+
{"foo", "bar"},
43+
{"foo2", "bar2"},
44+
}
45+
sort.Sort(LabelPairSorter(test))
46+
47+
expected := []LabelPair{
48+
{"foo", "bar"},
49+
{"foo2", "bar2"},
50+
{"foo3", "bar3"},
51+
}
52+
if diff := cmp.Diff(test, expected); diff != "" {
53+
t.Fatal(diff)
54+
}
55+
}
56+
57+
func TestToDTOLabelPair(t *testing.T) {
58+
test := []LabelPair{
59+
{"foo", "bar"},
60+
{"foo2", "bar2"},
61+
{"foo3", "bar3"},
62+
}
63+
expected := []*dto.LabelPair{
64+
{Name: proto.String("foo"), Value: proto.String("bar")},
65+
{Name: proto.String("foo2"), Value: proto.String("bar2")},
66+
{Name: proto.String("foo3"), Value: proto.String("bar3")},
67+
}
68+
if diff := cmp.Diff(ToDTOLabelPair(test), expected, cmpopts.IgnoreUnexported(dto.LabelPair{})); diff != "" {
69+
t.Fatal(diff)
70+
}
71+
}

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

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

22+
"github.com/prometheus/client_golang/prometheus/internal/fastdto"
23+
2224
dto "github.com/prometheus/client_model/go"
2325
"google.golang.org/protobuf/proto"
2426
"google.golang.org/protobuf/types/known/timestamppb"
@@ -216,28 +218,18 @@ func MakeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
216218
// Super fast path.
217219
return nil
218220
}
221+
222+
// Conversion copies all but strings.
223+
ret := fastdto.ToDTOLabelPair(desc.labelPairs)
219224
if len(desc.variableLabels.names) == 0 {
220225
// 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)
226+
return ret
235227
}
228+
236229
for i, outputIndex := range desc.variableLabelOrder {
237-
labelPairs[outputIndex].Value = proto.String(labelValues[i])
230+
ret[outputIndex].Value = &labelValues[i] // Reusing string, assuming it's safe.
238231
}
239-
240-
return labelPairs
232+
return ret
241233
}
242234

243235
// 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)