Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Performance of MakeLabelPairs #1734

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion prometheus/counter.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func NewCounter(opts CounterOpts) Counter {
if opts.now == nil {
opts.now = time.Now
}
result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: opts.now}
result := &counter{desc: desc, labelPairs: desc.labelPairs, now: opts.now}
result.init(result) // Init self-collection.
result.createdTs = timestamppb.New(opts.now())
return result
Expand Down
42 changes: 34 additions & 8 deletions prometheus/desc.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,16 @@ type Desc struct {
fqName string
// help provides some helpful information about this metric.
help string
// constLabelPairs contains precalculated DTO label pairs based on
// the constant labels.
constLabelPairs []*dto.LabelPair
// variableLabels contains names of labels and normalization function for
// which the metric maintains variable values.
variableLabels *compiledLabels
// variableLabelOrder maps variableLabels indexes to the position in the
// pre-computed labelPairs slice. This allows fast MakeLabelPair function
// that have to place ordered variable label values into pre-sorted labelPairs.
variableLabelOrder []int
// labelPairs contains the sorted DTO label pairs based on the constant labels
// and variable labels
labelPairs []*dto.LabelPair
// id is a hash of the values of the ConstLabels and fqName. This
// must be unique among all registered descriptors and can therefore be
// used as an identifier of the descriptor.
Expand Down Expand Up @@ -160,14 +164,33 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
}
d.dimHash = xxh.Sum64()

d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels))
d.labelPairs = make([]*dto.LabelPair, 0, len(constLabels)+len(d.variableLabels.names))
for n, v := range constLabels {
d.constLabelPairs = append(d.constLabelPairs, &dto.LabelPair{
d.labelPairs = append(d.labelPairs, &dto.LabelPair{
Name: proto.String(n),
Value: proto.String(v),
})
}
sort.Sort(internal.LabelPairSorter(d.constLabelPairs))
for _, labelName := range d.variableLabels.names {
d.labelPairs = append(d.labelPairs, &dto.LabelPair{
Name: proto.String(labelName),
})
}
sort.Sort(internal.LabelPairSorter(d.labelPairs))

d.variableLabelOrder = make([]int, len(d.variableLabels.names))
for outputIndex, pair := range d.labelPairs {
// Constant labels have values variable labels do not.
if pair.Value != nil {
continue
}
for sourceIndex, variableLabel := range d.variableLabels.names {
if variableLabel == pair.GetName() {
d.variableLabelOrder[sourceIndex] = outputIndex
}
}
}

return d
}

Expand All @@ -182,8 +205,11 @@ func NewInvalidDesc(err error) *Desc {
}

func (d *Desc) String() string {
lpStrings := make([]string, 0, len(d.constLabelPairs))
for _, lp := range d.constLabelPairs {
lpStrings := make([]string, 0, len(d.labelPairs))
for _, lp := range d.labelPairs {
if lp.Value == nil {
continue
}
lpStrings = append(
lpStrings,
fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()),
Expand Down
27 changes: 27 additions & 0 deletions prometheus/desc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package prometheus

import (
"fmt"
"testing"
)

Expand Down Expand Up @@ -61,3 +62,29 @@ func TestNewInvalidDesc_String(t *testing.T) {
t.Errorf("String: unexpected output: %s", desc.String())
}
}

func BenchmarkNewDesc(b *testing.B) {
for _, bm := range []struct {
labelCount int
descFunc func() *Desc
}{
{
labelCount: 1,
descFunc: new1LabelDescFunc,
},
{
labelCount: 3,
descFunc: new3LabelsDescFunc,
},
{
labelCount: 10,
descFunc: new10LabelsDescFunc,
},
} {
b.Run(fmt.Sprintf("labels=%v", bm.labelCount), func(b *testing.B) {
for i := 0; i < b.N; i++ {
bm.descFunc()
}
})
}
}
2 changes: 1 addition & 1 deletion prometheus/gauge.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func NewGauge(opts GaugeOpts) Gauge {
nil,
opts.ConstLabels,
)
result := &gauge{desc: desc, labelPairs: desc.constLabelPairs}
result := &gauge{desc: desc, labelPairs: desc.labelPairs}
result.init(result) // Init self-collection.
return result
}
Expand Down
7 changes: 1 addition & 6 deletions prometheus/histogram.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,12 +537,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, labelValues))
}

for _, n := range desc.variableLabels.names {
if n == bucketLabel {
panic(errBucketLabelNotAllowed)
}
}
for _, lp := range desc.constLabelPairs {
for _, lp := range desc.labelPairs {
if lp.GetName() == bucketLabel {
panic(errBucketLabelNotAllowed)
}
Expand Down
12 changes: 2 additions & 10 deletions prometheus/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -962,21 +962,13 @@ func checkDescConsistency(
}

// Is the desc consistent with the content of the metric?
lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label))
copy(lpsFromDesc, desc.constLabelPairs)
for _, l := range desc.variableLabels.names {
lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
Name: proto.String(l),
})
}
if len(lpsFromDesc) != len(dtoMetric.Label) {
if len(desc.labelPairs) != len(dtoMetric.Label) {
return fmt.Errorf(
"labels in collected metric %s %s are inconsistent with descriptor %s",
metricFamily.GetName(), dtoMetric, desc,
)
}
sort.Sort(internal.LabelPairSorter(lpsFromDesc))
for i, lpFromDesc := range lpsFromDesc {
for i, lpFromDesc := range desc.labelPairs {
lpFromMetric := dtoMetric.Label[i]
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
Expand Down
7 changes: 1 addition & 6 deletions prometheus/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, labelValues))
}

for _, n := range desc.variableLabels.names {
if n == quantileLabel {
panic(errQuantileLabelNotAllowed)
}
}
for _, lp := range desc.constLabelPairs {
for _, lp := range desc.labelPairs {
if lp.GetName() == quantileLabel {
panic(errQuantileLabelNotAllowed)
}
Expand Down
32 changes: 18 additions & 14 deletions prometheus/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@ package prometheus
import (
"errors"
"fmt"
"sort"
"time"
"unicode/utf8"

"github.com/prometheus/client_golang/prometheus/internal"

dto "github.com/prometheus/client_model/go"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
Expand Down Expand Up @@ -215,24 +212,31 @@ func populateMetric(
// This function is only needed for custom Metric implementations. See MetricVec
// example.
func MakeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
totalLen := len(desc.variableLabels.names) + len(desc.constLabelPairs)
if totalLen == 0 {
if len(desc.labelPairs) == 0 {
// Super fast path.
return nil
}
if len(desc.variableLabels.names) == 0 {
// Moderately fast path.
return desc.constLabelPairs
return desc.labelPairs
}
labelPairs := make([]*dto.LabelPair, 0, totalLen)
for i, l := range desc.variableLabels.names {
labelPairs = append(labelPairs, &dto.LabelPair{
Name: proto.String(l),
Value: proto.String(labelValues[i]),
})
labelPairs := make([]*dto.LabelPair, 0, len(desc.labelPairs))
for _, lp := range desc.labelPairs {
var labelToAdd *dto.LabelPair
// Variable labels have no value and need to be inserted with a new dto.LabelPair containing the labelValue.
if lp.Value == nil {
labelToAdd = &dto.LabelPair{
Name: lp.Name,
}
} else {
labelToAdd = lp
}
labelPairs = append(labelPairs, labelToAdd)
}
for i, outputIndex := range desc.variableLabelOrder {
labelPairs[outputIndex].Value = proto.String(labelValues[i])
}
labelPairs = append(labelPairs, desc.constLabelPairs...)
sort.Sort(internal.LabelPairSorter(labelPairs))

return labelPairs
}

Expand Down
Loading
Loading