Skip to content

Commit 829ec00

Browse files
committed
Replace constLabels with a full set of sorted labelPairs
Signed-off-by: Kyle Eckhart <[email protected]>
1 parent e729ba1 commit 829ec00

File tree

9 files changed

+199
-53
lines changed

9 files changed

+199
-53
lines changed

prometheus/counter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func NewCounter(opts CounterOpts) Counter {
9494
if opts.now == nil {
9595
opts.now = time.Now
9696
}
97-
result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: opts.now}
97+
result := &counter{desc: desc, labelPairs: desc.labelPairs, now: opts.now}
9898
result.init(result) // Init self-collection.
9999
result.createdTs = timestamppb.New(opts.now())
100100
return result

prometheus/desc.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,17 @@ type Desc struct {
4747
fqName string
4848
// help provides some helpful information about this metric.
4949
help string
50-
// constLabelPairs contains precalculated DTO label pairs based on
51-
// the constant labels.
52-
constLabelPairs []*dto.LabelPair
5350
// variableLabels contains names of labels and normalization function for
5451
// which the metric maintains variable values.
5552
variableLabels *compiledLabels
53+
// labelPairs contains the sorted DTO label pairs based on the constant labels
54+
// and variable labels
55+
labelPairs []*dto.LabelPair
56+
// variableLabelIndexesInLabelPairs holds all indexes variable labels in the
57+
// labelPairs with the expected index of the variableLabel. This makes it easy
58+
// to identify all variable labels in the labelPairs and where to get their value
59+
// from when given the variable label values
60+
variableLabelIndexesInLabelPairs map[int]int
5661
// id is a hash of the values of the ConstLabels and fqName. This
5762
// must be unique among all registered descriptors and can therefore be
5863
// used as an identifier of the descriptor.
@@ -160,14 +165,36 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
160165
}
161166
d.dimHash = xxh.Sum64()
162167

163-
d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels))
168+
d.labelPairs = make([]*dto.LabelPair, 0, len(constLabels)+len(d.variableLabels.names))
164169
for n, v := range constLabels {
165-
d.constLabelPairs = append(d.constLabelPairs, &dto.LabelPair{
170+
d.labelPairs = append(d.labelPairs, &dto.LabelPair{
166171
Name: proto.String(n),
167172
Value: proto.String(v),
168173
})
169174
}
170-
sort.Sort(internal.LabelPairSorter(d.constLabelPairs))
175+
for _, labelName := range d.variableLabels.names {
176+
d.labelPairs = append(d.labelPairs, &dto.LabelPair{
177+
Name: proto.String(labelName),
178+
})
179+
}
180+
sort.Sort(internal.LabelPairSorter(d.labelPairs))
181+
182+
// In order to facilitate mapping from the unsorted variable labels to
183+
// the sorted variable labels we generate a mapping from output labelPair
184+
// index -> variableLabel index for constructing the final label pairs later
185+
d.variableLabelIndexesInLabelPairs = make(map[int]int, len(d.variableLabels.names))
186+
for outputIndex, pair := range d.labelPairs {
187+
// Constant labels have values variable labels do not
188+
if pair.Value != nil {
189+
continue
190+
}
191+
for sourceIndex, variableLabel := range d.variableLabels.names {
192+
if variableLabel == pair.GetName() {
193+
d.variableLabelIndexesInLabelPairs[outputIndex] = sourceIndex
194+
}
195+
}
196+
}
197+
171198
return d
172199
}
173200

@@ -182,8 +209,11 @@ func NewInvalidDesc(err error) *Desc {
182209
}
183210

184211
func (d *Desc) String() string {
185-
lpStrings := make([]string, 0, len(d.constLabelPairs))
186-
for _, lp := range d.constLabelPairs {
212+
lpStrings := make([]string, 0, len(d.labelPairs))
213+
for _, lp := range d.labelPairs {
214+
if lp.Value == nil {
215+
continue
216+
}
187217
lpStrings = append(
188218
lpStrings,
189219
fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()),

prometheus/gauge.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func NewGauge(opts GaugeOpts) Gauge {
8282
nil,
8383
opts.ConstLabels,
8484
)
85-
result := &gauge{desc: desc, labelPairs: desc.constLabelPairs}
85+
result := &gauge{desc: desc, labelPairs: desc.labelPairs}
8686
result.init(result) // Init self-collection.
8787
return result
8888
}

prometheus/histogram.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -537,12 +537,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
537537
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, labelValues))
538538
}
539539

540-
for _, n := range desc.variableLabels.names {
541-
if n == bucketLabel {
542-
panic(errBucketLabelNotAllowed)
543-
}
544-
}
545-
for _, lp := range desc.constLabelPairs {
540+
for _, lp := range desc.labelPairs {
546541
if lp.GetName() == bucketLabel {
547542
panic(errBucketLabelNotAllowed)
548543
}

prometheus/registry.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -962,21 +962,13 @@ func checkDescConsistency(
962962
}
963963

964964
// Is the desc consistent with the content of the metric?
965-
lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label))
966-
copy(lpsFromDesc, desc.constLabelPairs)
967-
for _, l := range desc.variableLabels.names {
968-
lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
969-
Name: proto.String(l),
970-
})
971-
}
972-
if len(lpsFromDesc) != len(dtoMetric.Label) {
965+
if len(desc.labelPairs) != len(dtoMetric.Label) {
973966
return fmt.Errorf(
974967
"labels in collected metric %s %s are inconsistent with descriptor %s",
975968
metricFamily.GetName(), dtoMetric, desc,
976969
)
977970
}
978-
sort.Sort(internal.LabelPairSorter(lpsFromDesc))
979-
for i, lpFromDesc := range lpsFromDesc {
971+
for i, lpFromDesc := range desc.labelPairs {
980972
lpFromMetric := dtoMetric.Label[i]
981973
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
982974
lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {

prometheus/summary.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
196196
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, labelValues))
197197
}
198198

199-
for _, n := range desc.variableLabels.names {
200-
if n == quantileLabel {
201-
panic(errQuantileLabelNotAllowed)
202-
}
203-
}
204-
for _, lp := range desc.constLabelPairs {
199+
for _, lp := range desc.labelPairs {
205200
if lp.GetName() == quantileLabel {
206201
panic(errQuantileLabelNotAllowed)
207202
}

prometheus/value.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@ package prometheus
1616
import (
1717
"errors"
1818
"fmt"
19-
"sort"
2019
"time"
2120
"unicode/utf8"
2221

23-
"github.com/prometheus/client_golang/prometheus/internal"
24-
2522
dto "github.com/prometheus/client_model/go"
2623
"google.golang.org/protobuf/proto"
2724
"google.golang.org/protobuf/types/known/timestamppb"
@@ -215,24 +212,29 @@ func populateMetric(
215212
// This function is only needed for custom Metric implementations. See MetricVec
216213
// example.
217214
func MakeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
218-
totalLen := len(desc.variableLabels.names) + len(desc.constLabelPairs)
219-
if totalLen == 0 {
215+
if len(desc.labelPairs) == 0 {
220216
// Super fast path.
221217
return nil
222218
}
223219
if len(desc.variableLabels.names) == 0 {
224220
// Moderately fast path.
225-
return desc.constLabelPairs
221+
return desc.labelPairs
226222
}
227-
labelPairs := make([]*dto.LabelPair, 0, totalLen)
228-
for i, l := range desc.variableLabels.names {
229-
labelPairs = append(labelPairs, &dto.LabelPair{
230-
Name: proto.String(l),
231-
Value: proto.String(labelValues[i]),
232-
})
223+
labelPairs := make([]*dto.LabelPair, 0, len(desc.labelPairs))
224+
for i, 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+
variableLabelIndex := desc.variableLabelIndexesInLabelPairs[i]
229+
labelToAdd = &dto.LabelPair{
230+
Name: lp.Name,
231+
Value: proto.String(labelValues[variableLabelIndex]),
232+
}
233+
} else {
234+
labelToAdd = lp
235+
}
236+
labelPairs = append(labelPairs, labelToAdd)
233237
}
234-
labelPairs = append(labelPairs, desc.constLabelPairs...)
235-
sort.Sort(internal.LabelPairSorter(labelPairs))
236238
return labelPairs
237239
}
238240

prometheus/value_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
package prometheus
1515

1616
import (
17+
"reflect"
1718
"testing"
1819
"time"
1920

2021
dto "github.com/prometheus/client_model/go"
22+
"google.golang.org/protobuf/proto"
2123
"google.golang.org/protobuf/types/known/timestamppb"
2224
)
2325

@@ -108,3 +110,130 @@ func TestNewConstMetricWithCreatedTimestamp(t *testing.T) {
108110
})
109111
}
110112
}
113+
114+
func TestMakeLabelPairs(t *testing.T) {
115+
tests := []struct {
116+
name string
117+
desc *Desc
118+
labelValues []string
119+
want []*dto.LabelPair
120+
}{
121+
{
122+
name: "no labels",
123+
desc: NewDesc("metric-1", "", nil, nil),
124+
labelValues: nil,
125+
want: nil,
126+
},
127+
{
128+
name: "only constant labels",
129+
desc: NewDesc("metric-1", "", nil, map[string]string{
130+
"label-1": "1",
131+
"label-2": "2",
132+
"label-3": "3"}),
133+
labelValues: nil,
134+
want: []*dto.LabelPair{
135+
{Name: proto.String("label-1"), Value: proto.String("1")},
136+
{Name: proto.String("label-2"), Value: proto.String("2")},
137+
{Name: proto.String("label-3"), Value: proto.String("3")},
138+
},
139+
},
140+
{
141+
name: "only variable labels",
142+
desc: NewDesc("metric-1", "", []string{"var-label-1", "var-label-2", "var-label-3"}, nil),
143+
labelValues: []string{"1", "2", "3"},
144+
want: []*dto.LabelPair{
145+
{Name: proto.String("var-label-1"), Value: proto.String("1")},
146+
{Name: proto.String("var-label-2"), Value: proto.String("2")},
147+
{Name: proto.String("var-label-3"), Value: proto.String("3")},
148+
},
149+
},
150+
{
151+
name: "variable and const labels",
152+
desc: NewDesc("metric-1", "", []string{"var-label-1", "var-label-2", "var-label-3"}, map[string]string{
153+
"label-1": "1",
154+
"label-2": "2",
155+
"label-3": "3"}),
156+
labelValues: []string{"1", "2", "3"},
157+
want: []*dto.LabelPair{
158+
{Name: proto.String("label-1"), Value: proto.String("1")},
159+
{Name: proto.String("label-2"), Value: proto.String("2")},
160+
{Name: proto.String("label-3"), Value: proto.String("3")},
161+
{Name: proto.String("var-label-1"), Value: proto.String("1")},
162+
{Name: proto.String("var-label-2"), Value: proto.String("2")},
163+
{Name: proto.String("var-label-3"), Value: proto.String("3")},
164+
},
165+
},
166+
{
167+
name: "unsorted variable and const labels are sorted",
168+
desc: NewDesc("metric-1", "", []string{"var-label-3", "var-label-2", "var-label-1"}, map[string]string{
169+
"label-3": "3",
170+
"label-2": "2",
171+
"label-1": "1",
172+
}),
173+
labelValues: []string{"3", "2", "1"},
174+
want: []*dto.LabelPair{
175+
{Name: proto.String("label-1"), Value: proto.String("1")},
176+
{Name: proto.String("label-2"), Value: proto.String("2")},
177+
{Name: proto.String("label-3"), Value: proto.String("3")},
178+
{Name: proto.String("var-label-1"), Value: proto.String("1")},
179+
{Name: proto.String("var-label-2"), Value: proto.String("2")},
180+
{Name: proto.String("var-label-3"), Value: proto.String("3")},
181+
},
182+
},
183+
}
184+
for _, tt := range tests {
185+
t.Run(tt.name, func(t *testing.T) {
186+
if got := MakeLabelPairs(tt.desc, tt.labelValues); !reflect.DeepEqual(got, tt.want) {
187+
t.Errorf("%v != %v", got, tt.want)
188+
}
189+
})
190+
}
191+
}
192+
193+
func Benchmark_MakeLabelPairs(b *testing.B) {
194+
benchFunc := func(desc *Desc, variableLabelValues []string) {
195+
MakeLabelPairs(desc, variableLabelValues)
196+
}
197+
198+
benchmarks := []struct {
199+
name string
200+
bench func(desc *Desc, variableLabelValues []string)
201+
desc *Desc
202+
variableLabelValues []string
203+
}{
204+
{
205+
name: "1 label",
206+
desc: NewDesc(
207+
"metric",
208+
"help",
209+
[]string{"var-label-1"},
210+
Labels{"const-label-1": "value"}),
211+
variableLabelValues: []string{"value"},
212+
},
213+
{
214+
name: "3 labels",
215+
desc: NewDesc(
216+
"metric",
217+
"help",
218+
[]string{"var-label-1", "var-label-3", "var-label-2"},
219+
Labels{"const-label-1": "value", "const-label-3": "value", "const-label-2": "value"}),
220+
variableLabelValues: []string{"value", "value", "value"},
221+
},
222+
{
223+
name: "10 labels",
224+
desc: NewDesc(
225+
"metric",
226+
"help",
227+
[]string{"var-label-5", "var-label-1", "var-label-3", "var-label-2", "var-label-10", "var-label-4", "var-label-7", "var-label-8", "var-label-9"},
228+
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"}),
229+
variableLabelValues: []string{"value", "value", "value", "value", "value", "value", "value", "value", "value", "value"},
230+
},
231+
}
232+
for _, bm := range benchmarks {
233+
b.Run(bm.name, func(b *testing.B) {
234+
for i := 0; i < b.N; i++ {
235+
benchFunc(bm.desc, bm.variableLabelValues)
236+
}
237+
})
238+
}
239+
}

prometheus/wrap.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,20 @@ func (m *wrappingMetric) Write(out *dto.Metric) error {
188188

189189
func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc {
190190
constLabels := Labels{}
191-
for _, lp := range desc.constLabelPairs {
192-
constLabels[*lp.Name] = *lp.Value
191+
for _, lp := range desc.labelPairs {
192+
// Variable labels have no values
193+
if lp.Value != nil {
194+
constLabels[*lp.Name] = *lp.Value
195+
}
193196
}
194197
for ln, lv := range labels {
195198
if _, alreadyUsed := constLabels[ln]; alreadyUsed {
196199
return &Desc{
197-
fqName: desc.fqName,
198-
help: desc.help,
199-
variableLabels: desc.variableLabels,
200-
constLabelPairs: desc.constLabelPairs,
201-
err: fmt.Errorf("attempted wrapping with already existing label name %q", ln),
200+
fqName: desc.fqName,
201+
help: desc.help,
202+
variableLabels: desc.variableLabels,
203+
labelPairs: desc.labelPairs,
204+
err: fmt.Errorf("attempted wrapping with already existing label name %q", ln),
202205
}
203206
}
204207
constLabels[ln] = lv

0 commit comments

Comments
 (0)