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 all 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
53 changes: 41 additions & 12 deletions prometheus/desc.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import (
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/model"
"google.golang.org/protobuf/proto"

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

// Desc is the descriptor used by every Prometheus Metric. It is essentially
Expand All @@ -47,12 +45,15 @@ 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
// constLabelPairs contains the sorted DTO label pairs based on the constant labels
// and variable labels
constLabelPairs []*dto.LabelPair
// orderedLabels contains the sorted labels with necessary fields to construct the
// final label pairs when needed.
orderedLabels []labelMapping
// 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 All @@ -66,6 +67,21 @@ type Desc struct {
err error
}

type labelMapping struct {
constLabelIndex int

variableLabelIndex int
variableLabelName *string
}

func newLabelMapping() labelMapping {
return labelMapping{
constLabelIndex: -1,
variableLabelIndex: -1,
variableLabelName: nil,
}
}

// NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
// and will be reported on registration time. variableLabels and constLabels can
// be nil if no such labels should be set. fqName must not be empty.
Expand Down Expand Up @@ -133,7 +149,7 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
d.err = fmt.Errorf("%q is not a valid label name for metric %q", label, fqName)
return d
}
labelNames = append(labelNames, "$"+label)
labelNames = append(labelNames, label)
Copy link
Author

@kgeckhart kgeckhart Mar 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is what enables me to drop an extra sort. I can't see the reason variable labels need to be prefixed with a $ here but there might be something I'm missing.

labelNameSet[label] = struct{}{}
}
if len(labelNames) != len(labelNameSet) {
Expand Down Expand Up @@ -161,13 +177,26 @@ func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, const
d.dimHash = xxh.Sum64()

d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels))
for n, v := range constLabels {
d.constLabelPairs = append(d.constLabelPairs, &dto.LabelPair{
Name: proto.String(n),
Value: proto.String(v),
})
d.orderedLabels = make([]labelMapping, len(labelNames))
for i, n := range labelNames {
lm := newLabelMapping()
if l, ok := constLabels[n]; ok {
d.constLabelPairs = append(d.constLabelPairs, &dto.LabelPair{
Name: proto.String(n),
Value: proto.String(l),
})
lm.constLabelIndex = len(d.constLabelPairs) - 1
} else {
for variableLabelIndex, variableLabel := range variableLabels.labelNames() {
if variableLabel == n {
lm.variableLabelIndex = variableLabelIndex
lm.variableLabelName = proto.String(variableLabel)
}
}
}
d.orderedLabels[i] = lm
}
sort.Sort(internal.LabelPairSorter(d.constLabelPairs))

return d
}

Expand Down
29 changes: 29 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,31 @@ 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) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bm.descFunc()
}
})
}
}
35 changes: 19 additions & 16 deletions prometheus/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -962,28 +962,31 @@ 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.orderedLabels) != 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, lm := range desc.orderedLabels {
lpFromMetric := dtoMetric.Label[i]
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
return fmt.Errorf(
"labels in collected metric %s %s are inconsistent with descriptor %s",
metricFamily.GetName(), dtoMetric, desc,
)
if lm.constLabelIndex > -1 {
lpFromDesc := desc.constLabelPairs[lm.constLabelIndex]
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
return fmt.Errorf(
"labels in collected metric %s %s are inconsistent with descriptor %s",
metricFamily.GetName(), dtoMetric, desc,
)
}
} else {
variableLabelName := *lm.variableLabelName
if variableLabelName != lpFromMetric.GetName() {
return fmt.Errorf(
"labels in collected metric %s %s are inconsistent with descriptor %s",
metricFamily.GetName(), dtoMetric, desc,
)
}
}
}
return nil
Expand Down
24 changes: 11 additions & 13 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,25 @@ 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.orderedLabels) == 0 {
// Super fast path.
return nil
}
if len(desc.variableLabels.names) == 0 {
// Moderately fast path.
return desc.constLabelPairs
}
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, len(desc.orderedLabels))
for i, lm := range desc.orderedLabels {
if lm.constLabelIndex != -1 {
labelPairs[i] = desc.constLabelPairs[lm.constLabelIndex]
} else {
labelPairs[i] = &dto.LabelPair{
Name: lm.variableLabelName,
Value: proto.String(labelValues[lm.variableLabelIndex]),
}
}
}
labelPairs = append(labelPairs, desc.constLabelPairs...)
sort.Sort(internal.LabelPairSorter(labelPairs))
return labelPairs
}

Expand Down
Loading
Loading