Skip to content
Draft
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
20 changes: 17 additions & 3 deletions prometheus/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ func processMetric(
}
metricFamily, ok := metricFamiliesByName[desc.fqName]
if ok { // Existing name.
if metricFamily.GetHelp() != desc.help {
if !helpTextMatches(metricFamily.GetHelp(), desc.help) {
return fmt.Errorf(
"collected metric %s %s has help %q but should have %q",
desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
Expand Down Expand Up @@ -812,7 +812,7 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
for _, mf := range mfs {
existingMF, exists := metricFamiliesByName[mf.GetName()]
if exists {
if existingMF.GetHelp() != mf.GetHelp() {
if !helpTextMatches(existingMF.GetHelp(), mf.GetHelp()) {
errs = append(errs, fmt.Errorf(
"gathered metric family %s has help %q but should have %q",
mf.GetName(), mf.GetHelp(), existingMF.GetHelp(),
Expand Down Expand Up @@ -1001,7 +1001,7 @@ func checkDescConsistency(
desc *Desc,
) error {
// Desc help consistency with metric family help.
if metricFamily.GetHelp() != desc.help {
if !helpTextMatches(metricFamily.GetHelp(), desc.help) {
return fmt.Errorf(
"collected metric %s %s has help %q but should have %q",
metricFamily.GetName(), dtoMetric, metricFamily.GetHelp(), desc.help,
Expand Down Expand Up @@ -1036,6 +1036,20 @@ func checkDescConsistency(
return nil
}

// helpTextMatches preserves compatibility with collectors that changed only a
// final period in their help text. This is needed when applications using
// different client versions share a registry, but other help-text changes
// remain strict consistency errors.
func helpTextMatches(a, b string) bool {
if a == b {
return true
}
if len(a) > 1 && strings.HasSuffix(a, ".") && strings.TrimSuffix(a, ".") == b {
return true
}
return len(b) > 1 && strings.HasSuffix(b, ".") && strings.TrimSuffix(b, ".") == a
}

var _ TransactionalGatherer = &MultiTRegistry{}

// MultiTRegistry is a TransactionalGatherer that joins gathered metrics from multiple
Expand Down
36 changes: 36 additions & 0 deletions prometheus/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,42 @@ func TestHandler(t *testing.T) {
testHandler(t)
}

func TestGatherAllowsHelpTextFinalPeriodDifference(t *testing.T) {
registry := prometheus.NewPedanticRegistry()
registry.MustRegister(prometheus.NewGauge(prometheus.GaugeOpts{
Name: "period_compatibility",
Help: "help text",
ConstLabels: prometheus.Labels{"source": "old"},
}))
registry.MustRegister(uncheckedCollector{c: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "period_compatibility",
Help: "help text.",
ConstLabels: prometheus.Labels{"source": "new"},
})})

if _, err := registry.Gather(); err != nil {
t.Fatalf("Gather returned an error for a final-period-only difference: %v", err)
}
}

func TestGatherRejectsOtherHelpTextDifference(t *testing.T) {
registry := prometheus.NewPedanticRegistry()
registry.MustRegister(prometheus.NewGauge(prometheus.GaugeOpts{
Name: "period_compatibility_strict",
Help: "help text",
ConstLabels: prometheus.Labels{"source": "old"},
}))
registry.MustRegister(uncheckedCollector{c: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "period_compatibility_strict",
Help: "different help text",
ConstLabels: prometheus.Labels{"source": "new"},
})})

if _, err := registry.Gather(); err == nil {
t.Fatal("Gather accepted a non-period help-text difference")
}
}

func BenchmarkHandler(b *testing.B) {
for i := 0; i < b.N; i++ {
testHandler(b)
Expand Down
Loading