From 6cf871196245c37729a78b3da8d84b932645661e Mon Sep 17 00:00:00 2001 From: Bobby Date: Thu, 4 Jun 2026 20:19:51 +0700 Subject: [PATCH 1/2] Add registry option to skip metric sorting Signed-off-by: Bobby --- prometheus/internal/metric.go | 18 ++++++++++++++ prometheus/registry.go | 46 ++++++++++++++++++++++++++--------- prometheus/registry_test.go | 43 ++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 11 deletions(-) diff --git a/prometheus/internal/metric.go b/prometheus/internal/metric.go index 6515c1148..4532d83a5 100644 --- a/prometheus/internal/metric.go +++ b/prometheus/internal/metric.go @@ -83,6 +83,24 @@ func (s MetricSorter) Less(i, j int) bool { // MetricFamilies pruned and the remaining MetricFamilies sorted by name within // the slice, with the contained Metrics sorted within each MetricFamily. func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily { + return NormalizeMetricFamiliesWithSorting(metricFamiliesByName, true) +} + +// NormalizeMetricFamiliesWithSorting returns a MetricFamily slice with empty +// MetricFamilies pruned. If withSorting is true, it sorts the remaining +// MetricFamilies by name and sorts the contained Metrics within each +// MetricFamily. +func NormalizeMetricFamiliesWithSorting(metricFamiliesByName map[string]*dto.MetricFamily, withSorting bool) []*dto.MetricFamily { + if !withSorting { + result := make([]*dto.MetricFamily, 0, len(metricFamiliesByName)) + for _, mf := range metricFamiliesByName { + if len(mf.Metric) > 0 { + result = append(result, mf) + } + } + return result + } + for _, mf := range metricFamiliesByName { sort.Sort(MetricSorter(mf.Metric)) } diff --git a/prometheus/registry.go b/prometheus/registry.go index ed0681c8b..0e859eff8 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -65,10 +65,25 @@ func init() { // NewRegistry creates a new vanilla Registry without any Collectors // pre-registered. func NewRegistry() *Registry { + return NewRegistryWithOptions(RegistryOpts{}) +} + +// RegistryOpts configures a Registry. +type RegistryOpts struct { + // DisableMetricSorting skips sorting MetricFamilies and their Metrics in + // Gather. The gathered MetricFamilies remain valid for exposition, but their + // order is unspecified. + DisableMetricSorting bool +} + +// NewRegistryWithOptions creates a new vanilla Registry without any Collectors +// pre-registered. +func NewRegistryWithOptions(opts RegistryOpts) *Registry { return &Registry{ - collectorsByID: map[uint64]Collector{}, - descIDs: map[uint64]struct{}{}, - dimHashesByName: map[string]uint64{}, + collectorsByID: map[uint64]Collector{}, + descIDs: map[uint64]struct{}{}, + dimHashesByName: map[string]uint64{}, + disableMetricSorting: opts.DisableMetricSorting, } } @@ -83,7 +98,12 @@ func NewRegistry() *Registry { // Collectors and Metrics will only provide consistent Descs. This Registry is // useful to test the implementation of Collectors and Metrics. func NewPedanticRegistry() *Registry { - r := NewRegistry() + return NewPedanticRegistryWithOptions(RegistryOpts{}) +} + +// NewPedanticRegistryWithOptions is like NewPedanticRegistry but applies opts. +func NewPedanticRegistryWithOptions(opts RegistryOpts) *Registry { + r := NewRegistryWithOptions(opts) r.pedanticChecksEnabled = true return r } @@ -139,12 +159,14 @@ type Registerer interface { // interface. type Gatherer interface { // Gather calls the Collect method of the registered Collectors and then - // gathers the collected metrics into a lexicographically sorted slice - // of uniquely named MetricFamily protobufs. Gather ensures that the - // returned slice is valid and self-consistent so that it can be used - // for valid exposition. As an exception to the strict consistency - // requirements described for metric.Desc, Gather will tolerate - // different sets of label names for metrics of the same metric family. + // gathers the collected metrics into a slice of uniquely named + // MetricFamily protobufs. Registry sorts the returned MetricFamilies and + // their contained Metrics by default; callers can disable that with + // RegistryOpts.DisableMetricSorting. Gather ensures that the returned slice + // is valid and self-consistent so that it can be used for valid exposition. + // As an exception to the strict consistency requirements described for + // metric.Desc, Gather will tolerate different sets of label names for + // metrics of the same metric family. // // Even if an error occurs, Gather attempts to gather as many metrics as // possible. Hence, if a non-nil error is returned, the returned @@ -277,6 +299,7 @@ type Registry struct { dimHashesByName map[string]uint64 uncheckedCollectors []Collector pedanticChecksEnabled bool + disableMetricSorting bool } // Register implements Registerer. @@ -448,6 +471,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { wg sync.WaitGroup safeErrs = &SafeMultiError{} // To collect errors in a threadsafe way registeredDescIDs map[uint64]struct{} // Only used for pedantic checks + disableSorting = r.disableMetricSorting ) goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors) @@ -580,7 +604,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { } } - return internal.NormalizeMetricFamilies(metricFamiliesByName), safeErrs.errs.MaybeUnwrap() + return internal.NormalizeMetricFamiliesWithSorting(metricFamiliesByName, !disableSorting), safeErrs.errs.MaybeUnwrap() } // Describe implements Collector. diff --git a/prometheus/registry_test.go b/prometheus/registry_test.go index 379984fef..159a192c1 100644 --- a/prometheus/registry_test.go +++ b/prometheus/registry_test.go @@ -1312,6 +1312,49 @@ func (co *customCollector) Collect(ch chan<- prometheus.Metric) { co.collectFunc(ch) } +func TestRegistryDisableMetricSorting(t *testing.T) { + desc := prometheus.NewDesc("test_metric_order", "Test metric order.", []string{"letter"}, nil) + collect := func(ch chan<- prometheus.Metric) { + ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, 1, "z") + ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, 1, "a") + } + checkOrder := func(reg *prometheus.Registry, want ...string) { + t.Helper() + + mfs, err := reg.Gather() + if err != nil { + t.Fatalf("unexpected gather error: %v", err) + } + if len(mfs) != 1 { + t.Fatalf("expected one metric family, got %d", len(mfs)) + } + if got := mfs[0].GetName(); got != "test_metric_order" { + t.Fatalf("expected test_metric_order metric family, got %q", got) + } + if len(mfs[0].Metric) != len(want) { + t.Fatalf("expected %d metrics, got %d", len(want), len(mfs[0].Metric)) + } + for i, metric := range mfs[0].Metric { + if len(metric.Label) != 1 { + t.Fatalf("expected one label on metric %d, got %d", i, len(metric.Label)) + } + if got := metric.Label[0].GetValue(); got != want[i] { + t.Fatalf("metric %d label value = %q, want %q", i, got, want[i]) + } + } + } + + defaultReg := prometheus.NewRegistry() + defaultReg.MustRegister(&customCollector{collectFunc: collect}) + checkOrder(defaultReg, "a", "z") + + unsortedReg := prometheus.NewRegistryWithOptions(prometheus.RegistryOpts{ + DisableMetricSorting: true, + }) + unsortedReg.MustRegister(&customCollector{collectFunc: collect}) + checkOrder(unsortedReg, "z", "a") +} + // TestCollectorOnMetricPanic ensures that if a collector panics while collecting a metric, // the panic is recovered and the error is returned by Gather. It also checks that the metric // collected before the panic is still present in the gathered metrics. Additionally, From 9edc4f979715400f9ca4e93828b9ac9e3c1450cd Mon Sep 17 00:00:00 2001 From: Bobby Date: Thu, 4 Jun 2026 23:42:04 +0700 Subject: [PATCH 2/2] Remove redundant registry sorting variable Signed-off-by: Bobby --- prometheus/registry.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/prometheus/registry.go b/prometheus/registry.go index 0e859eff8..86731cf4d 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -471,7 +471,6 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { wg sync.WaitGroup safeErrs = &SafeMultiError{} // To collect errors in a threadsafe way registeredDescIDs map[uint64]struct{} // Only used for pedantic checks - disableSorting = r.disableMetricSorting ) goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors) @@ -604,7 +603,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { } } - return internal.NormalizeMetricFamiliesWithSorting(metricFamiliesByName, !disableSorting), safeErrs.errs.MaybeUnwrap() + return internal.NormalizeMetricFamiliesWithSorting(metricFamiliesByName, !r.disableMetricSorting), safeErrs.errs.MaybeUnwrap() } // Describe implements Collector.