Skip to content

Commit 644c80d

Browse files
authored
Do not allocate memory when there's no constraints (#1296)
Signed-off-by: Quentin Devos <[email protected]>
1 parent 553eb4c commit 644c80d

File tree

14 files changed

+231
-146
lines changed

14 files changed

+231
-146
lines changed

prometheus/benchmark_test.go

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,94 @@ import (
1818
"testing"
1919
)
2020

21-
func BenchmarkCounterWithLabelValues(b *testing.B) {
22-
m := NewCounterVec(
23-
CounterOpts{
24-
Name: "benchmark_counter",
25-
Help: "A counter to benchmark it.",
26-
},
27-
[]string{"one", "two", "three"},
28-
)
29-
b.ReportAllocs()
30-
b.ResetTimer()
31-
for i := 0; i < b.N; i++ {
32-
m.WithLabelValues("eins", "zwei", "drei").Inc()
21+
func BenchmarkCounter(b *testing.B) {
22+
type fns []func(*CounterVec) Counter
23+
24+
twoConstraint := func(_ string) string {
25+
return "two"
26+
}
27+
28+
deLV := func(m *CounterVec) Counter {
29+
return m.WithLabelValues("eins", "zwei", "drei")
30+
}
31+
frLV := func(m *CounterVec) Counter {
32+
return m.WithLabelValues("une", "deux", "trois")
33+
}
34+
nlLV := func(m *CounterVec) Counter {
35+
return m.WithLabelValues("een", "twee", "drie")
36+
}
37+
38+
deML := func(m *CounterVec) Counter {
39+
return m.With(Labels{"two": "zwei", "one": "eins", "three": "drei"})
40+
}
41+
frML := func(m *CounterVec) Counter {
42+
return m.With(Labels{"two": "deux", "one": "une", "three": "trois"})
43+
}
44+
nlML := func(m *CounterVec) Counter {
45+
return m.With(Labels{"two": "twee", "one": "een", "three": "drie"})
46+
}
47+
48+
deLabels := Labels{"two": "zwei", "one": "eins", "three": "drei"}
49+
dePML := func(m *CounterVec) Counter {
50+
return m.With(deLabels)
51+
}
52+
frLabels := Labels{"two": "deux", "one": "une", "three": "trois"}
53+
frPML := func(m *CounterVec) Counter {
54+
return m.With(frLabels)
55+
}
56+
nlLabels := Labels{"two": "twee", "one": "een", "three": "drie"}
57+
nlPML := func(m *CounterVec) Counter {
58+
return m.With(nlLabels)
59+
}
60+
61+
table := []struct {
62+
name string
63+
constraint LabelConstraint
64+
counters fns
65+
}{
66+
{"With Label Values", nil, fns{deLV}},
67+
{"With Label Values and Constraint", twoConstraint, fns{deLV}},
68+
{"With triple Label Values", nil, fns{deLV, frLV, nlLV}},
69+
{"With triple Label Values and Constraint", twoConstraint, fns{deLV, frLV, nlLV}},
70+
{"With repeated Label Values", nil, fns{deLV, deLV}},
71+
{"With repeated Label Values and Constraint", twoConstraint, fns{deLV, deLV}},
72+
{"With Mapped Labels", nil, fns{deML}},
73+
{"With Mapped Labels and Constraint", twoConstraint, fns{deML}},
74+
{"With triple Mapped Labels", nil, fns{deML, frML, nlML}},
75+
{"With triple Mapped Labels and Constraint", twoConstraint, fns{deML, frML, nlML}},
76+
{"With repeated Mapped Labels", nil, fns{deML, deML}},
77+
{"With repeated Mapped Labels and Constraint", twoConstraint, fns{deML, deML}},
78+
{"With Prepared Mapped Labels", nil, fns{dePML}},
79+
{"With Prepared Mapped Labels and Constraint", twoConstraint, fns{dePML}},
80+
{"With triple Prepared Mapped Labels", nil, fns{dePML, frPML, nlPML}},
81+
{"With triple Prepared Mapped Labels and Constraint", twoConstraint, fns{dePML, frPML, nlPML}},
82+
{"With repeated Prepared Mapped Labels", nil, fns{dePML, dePML}},
83+
{"With repeated Prepared Mapped Labels and Constraint", twoConstraint, fns{dePML, dePML}},
84+
}
85+
86+
for _, t := range table {
87+
b.Run(t.name, func(b *testing.B) {
88+
m := V2.NewCounterVec(
89+
CounterVecOpts{
90+
CounterOpts: CounterOpts{
91+
Name: "benchmark_counter",
92+
Help: "A counter to benchmark it.",
93+
},
94+
VariableLabels: ConstrainedLabels{
95+
ConstrainedLabel{Name: "one"},
96+
ConstrainedLabel{Name: "two", Constraint: t.constraint},
97+
ConstrainedLabel{Name: "three"},
98+
},
99+
},
100+
)
101+
b.ReportAllocs()
102+
b.ResetTimer()
103+
for i := 0; i < b.N; i++ {
104+
for _, fn := range t.counters {
105+
fn(m).Inc()
106+
}
107+
}
108+
})
33109
}
34110
}
35111

@@ -56,37 +132,6 @@ func BenchmarkCounterWithLabelValuesConcurrent(b *testing.B) {
56132
wg.Wait()
57133
}
58134

59-
func BenchmarkCounterWithMappedLabels(b *testing.B) {
60-
m := NewCounterVec(
61-
CounterOpts{
62-
Name: "benchmark_counter",
63-
Help: "A counter to benchmark it.",
64-
},
65-
[]string{"one", "two", "three"},
66-
)
67-
b.ReportAllocs()
68-
b.ResetTimer()
69-
for i := 0; i < b.N; i++ {
70-
m.With(Labels{"two": "zwei", "one": "eins", "three": "drei"}).Inc()
71-
}
72-
}
73-
74-
func BenchmarkCounterWithPreparedMappedLabels(b *testing.B) {
75-
m := NewCounterVec(
76-
CounterOpts{
77-
Name: "benchmark_counter",
78-
Help: "A counter to benchmark it.",
79-
},
80-
[]string{"one", "two", "three"},
81-
)
82-
b.ReportAllocs()
83-
b.ResetTimer()
84-
labels := Labels{"two": "zwei", "one": "eins", "three": "drei"}
85-
for i := 0; i < b.N; i++ {
86-
m.With(labels).Inc()
87-
}
88-
}
89-
90135
func BenchmarkCounterNoLabels(b *testing.B) {
91136
m := NewCounter(CounterOpts{
92137
Name: "benchmark_counter",

prometheus/counter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ func (v2) NewCounterVec(opts CounterVecOpts) *CounterVec {
202202
)
203203
return &CounterVec{
204204
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
205-
if len(lvs) != len(desc.variableLabels) {
206-
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.labelNames(), lvs))
205+
if len(lvs) != len(desc.variableLabels.names) {
206+
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, lvs))
207207
}
208208
result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: time.Now}
209209
result.init(result) // Init self-collection.

prometheus/desc.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ type Desc struct {
5252
constLabelPairs []*dto.LabelPair
5353
// variableLabels contains names of labels and normalization function for
5454
// which the metric maintains variable values.
55-
variableLabels ConstrainedLabels
55+
variableLabels *compiledLabels
5656
// id is a hash of the values of the ConstLabels and fqName. This
5757
// must be unique among all registered descriptors and can therefore be
5858
// used as an identifier of the descriptor.
@@ -93,7 +93,7 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
9393
d := &Desc{
9494
fqName: fqName,
9595
help: help,
96-
variableLabels: variableLabels.constrainedLabels(),
96+
variableLabels: variableLabels.compile(),
9797
}
9898
if !model.IsValidMetricName(model.LabelValue(fqName)) {
9999
d.err = fmt.Errorf("%q is not a valid metric name", fqName)
@@ -103,7 +103,7 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
103103
// their sorted label names) plus the fqName (at position 0).
104104
labelValues := make([]string, 1, len(constLabels)+1)
105105
labelValues[0] = fqName
106-
labelNames := make([]string, 0, len(constLabels)+len(d.variableLabels))
106+
labelNames := make([]string, 0, len(constLabels)+len(d.variableLabels.names))
107107
labelNameSet := map[string]struct{}{}
108108
// First add only the const label names and sort them...
109109
for labelName := range constLabels {
@@ -128,13 +128,13 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
128128
// Now add the variable label names, but prefix them with something that
129129
// cannot be in a regular label name. That prevents matching the label
130130
// dimension with a different mix between preset and variable labels.
131-
for _, label := range d.variableLabels {
132-
if !checkLabelName(label.Name) {
133-
d.err = fmt.Errorf("%q is not a valid label name for metric %q", label.Name, fqName)
131+
for _, label := range d.variableLabels.names {
132+
if !checkLabelName(label) {
133+
d.err = fmt.Errorf("%q is not a valid label name for metric %q", label, fqName)
134134
return d
135135
}
136-
labelNames = append(labelNames, "$"+label.Name)
137-
labelNameSet[label.Name] = struct{}{}
136+
labelNames = append(labelNames, "$"+label)
137+
labelNameSet[label] = struct{}{}
138138
}
139139
if len(labelNames) != len(labelNameSet) {
140140
d.err = fmt.Errorf("duplicate label names in constant and variable labels for metric %q", fqName)
@@ -189,11 +189,19 @@ func (d *Desc) String() string {
189189
fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()),
190190
)
191191
}
192+
vlStrings := make([]string, 0, len(d.variableLabels.names))
193+
for _, vl := range d.variableLabels.names {
194+
if fn, ok := d.variableLabels.labelConstraints[vl]; ok && fn != nil {
195+
vlStrings = append(vlStrings, fmt.Sprintf("c(%s)", vl))
196+
} else {
197+
vlStrings = append(vlStrings, vl)
198+
}
199+
}
192200
return fmt.Sprintf(
193-
"Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: %v}",
201+
"Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: {%s}}",
194202
d.fqName,
195203
d.help,
196204
strings.Join(lpStrings, ","),
197-
d.variableLabels,
205+
strings.Join(vlStrings, ","),
198206
)
199207
}

prometheus/examples_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,9 @@ func ExampleRegister() {
292292

293293
// Output:
294294
// taskCounter registered.
295-
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [{worker_id <nil>}]} has different label names or a different help string
295+
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: {worker_id}} has different label names or a different help string
296296
// taskCounter unregistered.
297-
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [{worker_id <nil>}]} has different label names or a different help string
297+
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: {worker_id}} has different label names or a different help string
298298
// taskCounterVec registered.
299299
// Worker initialization failed: inconsistent label cardinality: expected 1 label values but got 2 in []string{"42", "spurious arg"}
300300
// notMyCounter is nil.

prometheus/expvar_collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (e *expvarCollector) Collect(ch chan<- Metric) {
4848
continue
4949
}
5050
var v interface{}
51-
labels := make([]string, len(desc.variableLabels))
51+
labels := make([]string, len(desc.variableLabels.names))
5252
if err := json.Unmarshal([]byte(expVar.String()), &v); err != nil {
5353
ch <- NewInvalidMetric(desc, err)
5454
continue

prometheus/gauge.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ func (v2) NewGaugeVec(opts GaugeVecOpts) *GaugeVec {
166166
)
167167
return &GaugeVec{
168168
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
169-
if len(lvs) != len(desc.variableLabels) {
170-
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.labelNames(), lvs))
169+
if len(lvs) != len(desc.variableLabels.names) {
170+
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, lvs))
171171
}
172172
result := &gauge{desc: desc, labelPairs: MakeLabelPairs(desc, lvs)}
173173
result.init(result) // Init self-collection.

prometheus/gauge_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func TestGaugeFunc(t *testing.T) {
170170
func() float64 { return 3.1415 },
171171
)
172172

173-
if expected, got := `Desc{fqName: "test_name", help: "test help", constLabels: {a="1",b="2"}, variableLabels: []}`, gf.Desc().String(); expected != got {
173+
if expected, got := `Desc{fqName: "test_name", help: "test help", constLabels: {a="1",b="2"}, variableLabels: {}}`, gf.Desc().String(); expected != got {
174174
t.Errorf("expected %q, got %q", expected, got)
175175
}
176176

prometheus/histogram.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -499,12 +499,12 @@ func NewHistogram(opts HistogramOpts) Histogram {
499499
}
500500

501501
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
502-
if len(desc.variableLabels) != len(labelValues) {
503-
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.labelNames(), labelValues))
502+
if len(desc.variableLabels.names) != len(labelValues) {
503+
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, labelValues))
504504
}
505505

506-
for _, n := range desc.variableLabels {
507-
if n.Name == bucketLabel {
506+
for _, n := range desc.variableLabels.names {
507+
if n == bucketLabel {
508508
panic(errBucketLabelNotAllowed)
509509
}
510510
}
@@ -1230,7 +1230,7 @@ func NewConstHistogram(
12301230
if desc.err != nil {
12311231
return nil, desc.err
12321232
}
1233-
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
1233+
if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
12341234
return nil, err
12351235
}
12361236
return &constHistogram{

prometheus/labels.go

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,15 @@ import (
3232
// create a Desc.
3333
type Labels map[string]string
3434

35+
// LabelConstraint normalizes label values.
36+
type LabelConstraint func(string) string
37+
3538
// ConstrainedLabels represents a label name and its constrain function
3639
// to normalize label values. This type is commonly used when constructing
3740
// metric vector Collectors.
3841
type ConstrainedLabel struct {
3942
Name string
40-
Constraint func(string) string
41-
}
42-
43-
func (cl ConstrainedLabel) Constrain(v string) string {
44-
if cl.Constraint == nil {
45-
return v
46-
}
47-
return cl.Constraint(v)
43+
Constraint LabelConstraint
4844
}
4945

5046
// ConstrainableLabels is an interface that allows creating of labels that can
@@ -58,7 +54,7 @@ func (cl ConstrainedLabel) Constrain(v string) string {
5854
// },
5955
// })
6056
type ConstrainableLabels interface {
61-
constrainedLabels() ConstrainedLabels
57+
compile() *compiledLabels
6258
labelNames() []string
6359
}
6460

@@ -67,8 +63,20 @@ type ConstrainableLabels interface {
6763
// metric vector Collectors.
6864
type ConstrainedLabels []ConstrainedLabel
6965

70-
func (cls ConstrainedLabels) constrainedLabels() ConstrainedLabels {
71-
return cls
66+
func (cls ConstrainedLabels) compile() *compiledLabels {
67+
compiled := &compiledLabels{
68+
names: make([]string, len(cls)),
69+
labelConstraints: map[string]LabelConstraint{},
70+
}
71+
72+
for i, label := range cls {
73+
compiled.names[i] = label.Name
74+
if label.Constraint != nil {
75+
compiled.labelConstraints[label.Name] = label.Constraint
76+
}
77+
}
78+
79+
return compiled
7280
}
7381

7482
func (cls ConstrainedLabels) labelNames() []string {
@@ -92,18 +100,36 @@ func (cls ConstrainedLabels) labelNames() []string {
92100
// }
93101
type UnconstrainedLabels []string
94102

95-
func (uls UnconstrainedLabels) constrainedLabels() ConstrainedLabels {
96-
constrainedLabels := make([]ConstrainedLabel, len(uls))
97-
for i, l := range uls {
98-
constrainedLabels[i] = ConstrainedLabel{Name: l}
103+
func (uls UnconstrainedLabels) compile() *compiledLabels {
104+
return &compiledLabels{
105+
names: uls,
99106
}
100-
return constrainedLabels
101107
}
102108

103109
func (uls UnconstrainedLabels) labelNames() []string {
104110
return uls
105111
}
106112

113+
type compiledLabels struct {
114+
names []string
115+
labelConstraints map[string]LabelConstraint
116+
}
117+
118+
func (cls *compiledLabels) compile() *compiledLabels {
119+
return cls
120+
}
121+
122+
func (cls *compiledLabels) labelNames() []string {
123+
return cls.names
124+
}
125+
126+
func (cls *compiledLabels) constrain(labelName, value string) string {
127+
if fn, ok := cls.labelConstraints[labelName]; ok && fn != nil {
128+
return fn(value)
129+
}
130+
return value
131+
}
132+
107133
// reservedLabelPrefix is a prefix which is not legal in user-supplied
108134
// label names.
109135
const reservedLabelPrefix = "__"

0 commit comments

Comments
 (0)