Skip to content
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
18 changes: 18 additions & 0 deletions prometheus/internal/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
45 changes: 34 additions & 11 deletions prometheus/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -277,6 +299,7 @@ type Registry struct {
dimHashesByName map[string]uint64
uncheckedCollectors []Collector
pedanticChecksEnabled bool
disableMetricSorting bool
}

// Register implements Registerer.
Expand Down Expand Up @@ -580,7 +603,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
}
}

return internal.NormalizeMetricFamilies(metricFamiliesByName), safeErrs.errs.MaybeUnwrap()
return internal.NormalizeMetricFamiliesWithSorting(metricFamiliesByName, !r.disableMetricSorting), safeErrs.errs.MaybeUnwrap()
}

// Describe implements Collector.
Expand Down
43 changes: 43 additions & 0 deletions prometheus/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading