From b13eebfd7df79df01f7f798c3cfb7d565f0a1117 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Tue, 29 Jul 2025 11:47:21 -0400 Subject: [PATCH 01/26] add combined metrics package --- vms/evm/metrics/metricstest/metrics.go | 30 +++ vms/evm/metrics/prometheus/enabled_test.go | 29 +++ vms/evm/metrics/prometheus/interfaces.go | 14 ++ vms/evm/metrics/prometheus/prometheus.go | 209 ++++++++++++++++++ vms/evm/metrics/prometheus/prometheus_test.go | 169 ++++++++++++++ 5 files changed, 451 insertions(+) create mode 100644 vms/evm/metrics/metricstest/metrics.go create mode 100644 vms/evm/metrics/prometheus/enabled_test.go create mode 100644 vms/evm/metrics/prometheus/interfaces.go create mode 100644 vms/evm/metrics/prometheus/prometheus.go create mode 100644 vms/evm/metrics/prometheus/prometheus_test.go diff --git a/vms/evm/metrics/metricstest/metrics.go b/vms/evm/metrics/metricstest/metrics.go new file mode 100644 index 000000000000..42416e696b81 --- /dev/null +++ b/vms/evm/metrics/metricstest/metrics.go @@ -0,0 +1,30 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package metricstest + +import ( + "sync" + "testing" + + "github.com/ava-labs/libevm/metrics" +) + +var metricsLock sync.Mutex + +// WithMetrics enables go-ethereum metrics globally for the test. +// If the [metrics.Enabled] is already true, nothing is done. +// Otherwise, it is set to true and is reverted to false when the test finishes. +func WithMetrics(t *testing.T) { + metricsLock.Lock() + t.Cleanup(func() { + metricsLock.Unlock() + }) + if metrics.Enabled { + return + } + metrics.Enabled = true + t.Cleanup(func() { + metrics.Enabled = false + }) +} diff --git a/vms/evm/metrics/prometheus/enabled_test.go b/vms/evm/metrics/prometheus/enabled_test.go new file mode 100644 index 000000000000..a8c0b9a76dc7 --- /dev/null +++ b/vms/evm/metrics/prometheus/enabled_test.go @@ -0,0 +1,29 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package prometheus_test + +import ( + "testing" + + // NOTE: This test assumes that there are no imported packages that might + // change the default value of [metrics.Enabled]. It is therefore in package + // `prometheus_test` in case any other tests modify the variable. If any + // imports here or in the implementation do actually do so then this test + // may have false negatives. + "github.com/stretchr/testify/assert" + + "github.com/ava-labs/libevm/metrics" +) + +func TestMetricsEnabledByDefault(t *testing.T) { + assert.True(t, metrics.Enabled, "libevm/metrics.Enabled") + + switch m := metrics.NewCounter().(type) { + case metrics.NilCounter: + t.Errorf("metrics.NewCounter() got %T; want %T", m, new(metrics.StandardCounter)) + case *metrics.StandardCounter: + default: + t.Errorf("metrics.NewCounter() got unknown type %T", m) + } +} diff --git a/vms/evm/metrics/prometheus/interfaces.go b/vms/evm/metrics/prometheus/interfaces.go new file mode 100644 index 000000000000..03c45ca6df27 --- /dev/null +++ b/vms/evm/metrics/prometheus/interfaces.go @@ -0,0 +1,14 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package prometheus + +import "github.com/ava-labs/libevm/metrics" + +var _ Registry = metrics.Registry(nil) + +type Registry interface { + // Call the given function for each registered metric. + Each(func(string, any)) + // Get the metric by the given name or nil if none is registered. + Get(string) any +} diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go new file mode 100644 index 000000000000..feb2a9377082 --- /dev/null +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -0,0 +1,209 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package prometheus + +import ( + "errors" + "fmt" + "sort" + "strings" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/ava-labs/libevm/metrics" + + dto "github.com/prometheus/client_model/go" +) + +// Gatherer implements [prometheus.Gatherer] interface by +// gathering all metrics from the given Prometheus registry. +type Gatherer struct { + registry Registry +} + +var _ prometheus.Gatherer = (*Gatherer)(nil) + +// NewGatherer returns a [Gatherer] using the given registry. +func NewGatherer(registry Registry) *Gatherer { + return &Gatherer{ + registry: registry, + } +} + +// Gather gathers metrics from the registry and converts them to +// a slice of metric families. +func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { + // Gather and pre-sort the metrics to avoid random listings + var names []string + g.registry.Each(func(name string, i any) { + names = append(names, name) + }) + sort.Strings(names) + + mfs = make([]*dto.MetricFamily, 0, len(names)) + for _, name := range names { + mf, err := metricFamily(g.registry, name) + if err != nil { + if errors.Is(err, errMetricSkip) { + continue + } + return nil, err + } + mfs = append(mfs, mf) + } + + return mfs, nil +} + +var ( + errMetricSkip = errors.New("metric skipped") + errMetricTypeNotSupported = errors.New("metric type is not supported") +) + +func ptrTo[T any](x T) *T { return &x } + +func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err error) { + metric := registry.Get(name) + name = strings.ReplaceAll(name, "/", "_") + + switch m := metric.(type) { + case metrics.NilCounter, metrics.NilCounterFloat64, metrics.NilEWMA, + metrics.NilGauge, metrics.NilGaugeFloat64, metrics.NilGaugeInfo, + metrics.NilHealthcheck, metrics.NilHistogram, metrics.NilMeter, + metrics.NilResettingTimer, metrics.NilSample, metrics.NilTimer: + return nil, fmt.Errorf("%w: %q metric is nil", errMetricSkip, name) + + case metrics.Counter: + return &dto.MetricFamily{ + Name: &name, + Type: dto.MetricType_COUNTER.Enum(), + Metric: []*dto.Metric{{ + Counter: &dto.Counter{ + Value: ptrTo(float64(m.Snapshot().Count())), + }, + }}, + }, nil + case metrics.CounterFloat64: + return &dto.MetricFamily{ + Name: &name, + Type: dto.MetricType_COUNTER.Enum(), + Metric: []*dto.Metric{{ + Counter: &dto.Counter{ + Value: ptrTo(m.Snapshot().Count()), + }, + }}, + }, nil + case metrics.Gauge: + return &dto.MetricFamily{ + Name: &name, + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{{ + Gauge: &dto.Gauge{ + Value: ptrTo(float64(m.Snapshot().Value())), + }, + }}, + }, nil + case metrics.GaugeFloat64: + return &dto.MetricFamily{ + Name: &name, + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{{ + Gauge: &dto.Gauge{ + Value: ptrTo(m.Snapshot().Value()), + }, + }}, + }, nil + case metrics.Histogram: + snapshot := m.Snapshot() + + quantiles := []float64{.5, .75, .95, .99, .999, .9999} + thresholds := snapshot.Percentiles(quantiles) + dtoQuantiles := make([]*dto.Quantile, len(quantiles)) + for i := range thresholds { + dtoQuantiles[i] = &dto.Quantile{ + Quantile: ptrTo(quantiles[i]), + Value: ptrTo(thresholds[i]), + } + } + + return &dto.MetricFamily{ + Name: &name, + Type: dto.MetricType_SUMMARY.Enum(), + Metric: []*dto.Metric{{ + Summary: &dto.Summary{ + SampleCount: ptrTo(uint64(snapshot.Count())), //nolint:gosec + SampleSum: ptrTo(float64(snapshot.Sum())), + Quantile: dtoQuantiles, + }, + }}, + }, nil + case metrics.Meter: + return &dto.MetricFamily{ + Name: &name, + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{{ + Gauge: &dto.Gauge{ + Value: ptrTo(float64(m.Snapshot().Count())), + }, + }}, + }, nil + case metrics.Timer: + snapshot := m.Snapshot() + + quantiles := []float64{.5, .75, .95, .99, .999, .9999} + thresholds := snapshot.Percentiles(quantiles) + dtoQuantiles := make([]*dto.Quantile, len(quantiles)) + for i := range thresholds { + dtoQuantiles[i] = &dto.Quantile{ + Quantile: ptrTo(quantiles[i]), + Value: ptrTo(thresholds[i]), + } + } + + return &dto.MetricFamily{ + Name: &name, + Type: dto.MetricType_SUMMARY.Enum(), + Metric: []*dto.Metric{{ + Summary: &dto.Summary{ + SampleCount: ptrTo(uint64(snapshot.Count())), //nolint:gosec + SampleSum: ptrTo(float64(snapshot.Sum())), + Quantile: dtoQuantiles, + }, + }}, + }, nil + case metrics.ResettingTimer: + snapshot := m.Snapshot() + count := snapshot.Count() + if count == 0 { + return nil, fmt.Errorf("%w: %q resetting timer metric count is zero", errMetricSkip, name) + } + + pvShortPercent := []float64{50, 95, 99} + thresholds := snapshot.Percentiles(pvShortPercent) + dtoQuantiles := make([]*dto.Quantile, len(pvShortPercent)) + for i := range pvShortPercent { + dtoQuantiles[i] = &dto.Quantile{ + Quantile: ptrTo(pvShortPercent[i]), + Value: ptrTo(thresholds[i]), + } + } + + return &dto.MetricFamily{ + Name: &name, + Type: dto.MetricType_SUMMARY.Enum(), + Metric: []*dto.Metric{{ + Summary: &dto.Summary{ + SampleCount: ptrTo(uint64(count)), //nolint:gosec + SampleSum: ptrTo(float64(count) * snapshot.Mean()), + Quantile: dtoQuantiles, + }, + }}, + }, nil + case metrics.GaugeInfo: + // TODO(qdm12) handle this somehow maybe with dto.MetricType_UNTYPED + return nil, fmt.Errorf("%w: %q is a %T", errMetricSkip, name, metric) + default: + return nil, fmt.Errorf("%w: metric %q type %T", errMetricTypeNotSupported, name, metric) + } +} diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go new file mode 100644 index 000000000000..ef6d485384a1 --- /dev/null +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -0,0 +1,169 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package prometheus + +import ( + "strings" + "testing" + "time" + + "github.com/prometheus/common/expfmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/vms/evm/metrics/metricstest" + "github.com/ava-labs/libevm/metrics" +) + +func TestGatherer_Gather(t *testing.T) { + metricstest.WithMetrics(t) + + registry := metrics.NewRegistry() + register := func(t *testing.T, name string, collector any) { + t.Helper() + err := registry.Register(name, collector) + require.NoErrorf(t, err, "registering collector %q", name) + } + + counter := metrics.NewCounter() + counter.Inc(12345) + register(t, "test/counter", counter) + + counterFloat64 := metrics.NewCounterFloat64() + counterFloat64.Inc(1.1) + register(t, "test/counter_float64", counterFloat64) + + gauge := metrics.NewGauge() + gauge.Update(23456) + register(t, "test/gauge", gauge) + + gaugeFloat64 := metrics.NewGaugeFloat64() + gaugeFloat64.Update(34567.89) + register(t, "test/gauge_float64", gaugeFloat64) + + gaugeInfo := metrics.NewGaugeInfo() + gaugeInfo.Update(metrics.GaugeInfoValue{"key": "value"}) + register(t, "test/gauge_info", gaugeInfo) // skipped + + sample := metrics.NewUniformSample(1028) + histogram := metrics.NewHistogram(sample) + register(t, "test/histogram", histogram) + + meter := metrics.NewMeter() + t.Cleanup(meter.Stop) + meter.Mark(9999999) + register(t, "test/meter", meter) + + timer := metrics.NewTimer() + t.Cleanup(timer.Stop) + timer.Update(20 * time.Millisecond) + timer.Update(21 * time.Millisecond) + timer.Update(22 * time.Millisecond) + timer.Update(120 * time.Millisecond) + timer.Update(23 * time.Millisecond) + timer.Update(24 * time.Millisecond) + register(t, "test/timer", timer) + + resettingTimer := metrics.NewResettingTimer() + register(t, "test/resetting_timer", resettingTimer) + resettingTimer.Update(time.Second) // must be after register call + + emptyResettingTimer := metrics.NewResettingTimer() + register(t, "test/empty_resetting_timer", emptyResettingTimer) + + emptyResettingTimer.Update(time.Second) // no effect because of snapshot below + register(t, "test/empty_resetting_timer_snapshot", emptyResettingTimer.Snapshot()) + + registerNilMetrics(t, register) + + gatherer := NewGatherer(registry) + + families, err := gatherer.Gather() + require.NoError(t, err) + + const expectedString = ` +# TYPE test_counter counter +test_counter 12345 +# TYPE test_counter_float64 counter +test_counter_float64 1.1 +# TYPE test_gauge gauge +test_gauge 23456 +# TYPE test_gauge_float64 gauge +test_gauge_float64 34567.89 +# TYPE test_histogram summary +test_histogram{quantile="0.5"} 0 +test_histogram{quantile="0.75"} 0 +test_histogram{quantile="0.95"} 0 +test_histogram{quantile="0.99"} 0 +test_histogram{quantile="0.999"} 0 +test_histogram{quantile="0.9999"} 0 +test_histogram_sum 0 +test_histogram_count 0 +# TYPE test_meter gauge +test_meter 9.999999e+06 +# TYPE test_resetting_timer summary +test_resetting_timer{quantile="50"} 1e+09 +test_resetting_timer{quantile="95"} 1e+09 +test_resetting_timer{quantile="99"} 1e+09 +test_resetting_timer_sum 1e+09 +test_resetting_timer_count 1 +# TYPE test_timer summary +test_timer{quantile="0.5"} 2.25e+07 +test_timer{quantile="0.75"} 4.8e+07 +test_timer{quantile="0.95"} 1.2e+08 +test_timer{quantile="0.99"} 1.2e+08 +test_timer{quantile="0.999"} 1.2e+08 +test_timer{quantile="0.9999"} 1.2e+08 +test_timer_sum 2.3e+08 +test_timer_count 6 +` + var ( + stringReader = strings.NewReader(expectedString) + parser expfmt.TextParser + ) + expectedMetrics, err := parser.TextToMetricFamilies(stringReader) + require.NoError(t, err) + + assert.Len(t, families, len(expectedMetrics)) + for i, got := range families { + require.NotNil(t, *got.Name) + + want := expectedMetrics[*got.Name] + assert.Equal(t, want, got, i) + } + + register(t, "unsupported", metrics.NewHealthcheck(nil)) + families, err = gatherer.Gather() + assert.ErrorIs(t, err, errMetricTypeNotSupported) + assert.Empty(t, families) +} + +func registerNilMetrics(t *testing.T, register func(t *testing.T, name string, collector any)) { + metrics.Enabled = false + defer func() { metrics.Enabled = true }() + nilCounter := metrics.NewCounter() + register(t, "nil/counter", nilCounter) + nilCounterFloat64 := metrics.NewCounterFloat64() + register(t, "nil/counter_float64", nilCounterFloat64) + nilEWMA := &metrics.NilEWMA{} + register(t, "nil/ewma", nilEWMA) + nilGauge := metrics.NewGauge() + register(t, "nil/gauge", nilGauge) + nilGaugeFloat64 := metrics.NewGaugeFloat64() + register(t, "nil/gauge_float64", nilGaugeFloat64) + nilGaugeInfo := metrics.NewGaugeInfo() + register(t, "nil/gauge_info", nilGaugeInfo) + nilHealthcheck := metrics.NewHealthcheck(nil) + register(t, "nil/healthcheck", nilHealthcheck) + nilHistogram := metrics.NewHistogram(nil) + register(t, "nil/histogram", nilHistogram) + nilMeter := metrics.NewMeter() + register(t, "nil/meter", nilMeter) + nilResettingTimer := metrics.NewResettingTimer() + register(t, "nil/resetting_timer", nilResettingTimer) + nilSample := metrics.NewUniformSample(1028) + register(t, "nil/sample", nilSample) + nilTimer := metrics.NewTimer() + register(t, "nil/timer", nilTimer) +} From ff472523941e2ebc2012c05cddbb9930712b0645 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Tue, 29 Jul 2025 11:53:33 -0400 Subject: [PATCH 02/26] support GaugeInfo from subnet-evm --- vms/evm/metrics/prometheus/prometheus.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index feb2a9377082..e0d503871ae0 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -44,10 +44,10 @@ func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { mfs = make([]*dto.MetricFamily, 0, len(names)) for _, name := range names { mf, err := metricFamily(g.registry, name) - if err != nil { - if errors.Is(err, errMetricSkip) { - continue - } + switch { + case errors.Is(err, errMetricSkip): + continue + case err != nil: return nil, err } mfs = append(mfs, mf) @@ -73,7 +73,6 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err metrics.NilHealthcheck, metrics.NilHistogram, metrics.NilMeter, metrics.NilResettingTimer, metrics.NilSample, metrics.NilTimer: return nil, fmt.Errorf("%w: %q metric is nil", errMetricSkip, name) - case metrics.Counter: return &dto.MetricFamily{ Name: &name, @@ -114,6 +113,8 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err }, }}, }, nil + case metrics.GaugeInfo: + return nil, fmt.Errorf("%w: %q is a %T", errMetricSkip, name, m) case metrics.Histogram: snapshot := m.Snapshot() @@ -200,9 +201,6 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err }, }}, }, nil - case metrics.GaugeInfo: - // TODO(qdm12) handle this somehow maybe with dto.MetricType_UNTYPED - return nil, fmt.Errorf("%w: %q is a %T", errMetricSkip, name, metric) default: return nil, fmt.Errorf("%w: metric %q type %T", errMetricTypeNotSupported, name, metric) } From 45f25b0d25d40d8d8dd7347a85e7cacb7fbc717d Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Tue, 29 Jul 2025 14:21:12 -0400 Subject: [PATCH 03/26] lint update --- vms/evm/metrics/prometheus/enabled_test.go | 11 +++++------ vms/evm/metrics/prometheus/interfaces.go | 1 + vms/evm/metrics/prometheus/prometheus.go | 15 +++++++-------- vms/evm/metrics/prometheus/prometheus_test.go | 11 +++++------ 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/vms/evm/metrics/prometheus/enabled_test.go b/vms/evm/metrics/prometheus/enabled_test.go index a8c0b9a76dc7..f31605c15bf3 100644 --- a/vms/evm/metrics/prometheus/enabled_test.go +++ b/vms/evm/metrics/prometheus/enabled_test.go @@ -6,24 +6,23 @@ package prometheus_test import ( "testing" + "github.com/ava-labs/libevm/metrics" // NOTE: This test assumes that there are no imported packages that might // change the default value of [metrics.Enabled]. It is therefore in package // `prometheus_test` in case any other tests modify the variable. If any // imports here or in the implementation do actually do so then this test // may have false negatives. - "github.com/stretchr/testify/assert" - - "github.com/ava-labs/libevm/metrics" + "github.com/stretchr/testify/require" ) func TestMetricsEnabledByDefault(t *testing.T) { - assert.True(t, metrics.Enabled, "libevm/metrics.Enabled") + require.True(t, metrics.Enabled, "libevm/metrics.Enabled") switch m := metrics.NewCounter().(type) { case metrics.NilCounter: - t.Errorf("metrics.NewCounter() got %T; want %T", m, new(metrics.StandardCounter)) + require.Failf(t, "unexpected type", "metrics.NewCounter() got %T; want %T", m, new(metrics.StandardCounter)) case *metrics.StandardCounter: default: - t.Errorf("metrics.NewCounter() got unknown type %T", m) + require.Failf(t, "unexpected type", "metrics.NewCounter() got unknown type %T", m) } } diff --git a/vms/evm/metrics/prometheus/interfaces.go b/vms/evm/metrics/prometheus/interfaces.go index 03c45ca6df27..b8aa993453c0 100644 --- a/vms/evm/metrics/prometheus/interfaces.go +++ b/vms/evm/metrics/prometheus/interfaces.go @@ -1,5 +1,6 @@ // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. + package prometheus import "github.com/ava-labs/libevm/metrics" diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index e0d503871ae0..0745e90509d2 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -6,12 +6,11 @@ package prometheus import ( "errors" "fmt" - "sort" + "slices" "strings" - "github.com/prometheus/client_golang/prometheus" - "github.com/ava-labs/libevm/metrics" + "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) @@ -36,10 +35,10 @@ func NewGatherer(registry Registry) *Gatherer { func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { // Gather and pre-sort the metrics to avoid random listings var names []string - g.registry.Each(func(name string, i any) { + g.registry.Each(func(name string, _ any) { names = append(names, name) }) - sort.Strings(names) + slices.Sort(names) mfs = make([]*dto.MetricFamily, 0, len(names)) for _, name := range names { @@ -133,7 +132,7 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err Type: dto.MetricType_SUMMARY.Enum(), Metric: []*dto.Metric{{ Summary: &dto.Summary{ - SampleCount: ptrTo(uint64(snapshot.Count())), //nolint:gosec + SampleCount: ptrTo(uint64(snapshot.Count())), SampleSum: ptrTo(float64(snapshot.Sum())), Quantile: dtoQuantiles, }, @@ -167,7 +166,7 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err Type: dto.MetricType_SUMMARY.Enum(), Metric: []*dto.Metric{{ Summary: &dto.Summary{ - SampleCount: ptrTo(uint64(snapshot.Count())), //nolint:gosec + SampleCount: ptrTo(uint64(snapshot.Count())), SampleSum: ptrTo(float64(snapshot.Sum())), Quantile: dtoQuantiles, }, @@ -195,7 +194,7 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err Type: dto.MetricType_SUMMARY.Enum(), Metric: []*dto.Metric{{ Summary: &dto.Summary{ - SampleCount: ptrTo(uint64(count)), //nolint:gosec + SampleCount: ptrTo(uint64(count)), SampleSum: ptrTo(float64(count) * snapshot.Mean()), Quantile: dtoQuantiles, }, diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index ef6d485384a1..fbed23040e6e 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -8,12 +8,11 @@ import ( "testing" "time" + "github.com/ava-labs/libevm/metrics" "github.com/prometheus/common/expfmt" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/vms/evm/metrics/metricstest" - "github.com/ava-labs/libevm/metrics" ) func TestGatherer_Gather(t *testing.T) { @@ -125,18 +124,18 @@ test_timer_count 6 expectedMetrics, err := parser.TextToMetricFamilies(stringReader) require.NoError(t, err) - assert.Len(t, families, len(expectedMetrics)) + require.Len(t, families, len(expectedMetrics)) for i, got := range families { require.NotNil(t, *got.Name) want := expectedMetrics[*got.Name] - assert.Equal(t, want, got, i) + require.Equal(t, want, got, i) } register(t, "unsupported", metrics.NewHealthcheck(nil)) families, err = gatherer.Gather() - assert.ErrorIs(t, err, errMetricTypeNotSupported) - assert.Empty(t, families) + require.ErrorIs(t, err, errMetricTypeNotSupported) + require.Empty(t, families) } func registerNilMetrics(t *testing.T, register func(t *testing.T, name string, collector any)) { From 9a08a5868f1fe60e6ff15ae4027f95506d0debda Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:24:00 -0400 Subject: [PATCH 04/26] Update vms/evm/metrics/metricstest/metrics.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/metricstest/metrics.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vms/evm/metrics/metricstest/metrics.go b/vms/evm/metrics/metricstest/metrics.go index 42416e696b81..e63f91032b3f 100644 --- a/vms/evm/metrics/metricstest/metrics.go +++ b/vms/evm/metrics/metricstest/metrics.go @@ -17,9 +17,7 @@ var metricsLock sync.Mutex // Otherwise, it is set to true and is reverted to false when the test finishes. func WithMetrics(t *testing.T) { metricsLock.Lock() - t.Cleanup(func() { - metricsLock.Unlock() - }) + t.Cleanup(metricsLock.Unlock) if metrics.Enabled { return } From d5e51fe55dc1eb392b49f1dc3719a4a6a29c471f Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:24:25 -0400 Subject: [PATCH 05/26] Update vms/evm/metrics/metricstest/metrics.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/metricstest/metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/evm/metrics/metricstest/metrics.go b/vms/evm/metrics/metricstest/metrics.go index e63f91032b3f..edd8a30ad29d 100644 --- a/vms/evm/metrics/metricstest/metrics.go +++ b/vms/evm/metrics/metricstest/metrics.go @@ -15,7 +15,7 @@ var metricsLock sync.Mutex // WithMetrics enables go-ethereum metrics globally for the test. // If the [metrics.Enabled] is already true, nothing is done. // Otherwise, it is set to true and is reverted to false when the test finishes. -func WithMetrics(t *testing.T) { +func WithMetrics(t testing.TB) { metricsLock.Lock() t.Cleanup(metricsLock.Unlock) if metrics.Enabled { From c1626678e783ced57ef980987fa2c403e99f0dd1 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:25:06 -0400 Subject: [PATCH 06/26] Update vms/evm/metrics/prometheus/enabled_test.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/enabled_test.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/vms/evm/metrics/prometheus/enabled_test.go b/vms/evm/metrics/prometheus/enabled_test.go index f31605c15bf3..0f772a655b7a 100644 --- a/vms/evm/metrics/prometheus/enabled_test.go +++ b/vms/evm/metrics/prometheus/enabled_test.go @@ -17,12 +17,5 @@ import ( func TestMetricsEnabledByDefault(t *testing.T) { require.True(t, metrics.Enabled, "libevm/metrics.Enabled") - - switch m := metrics.NewCounter().(type) { - case metrics.NilCounter: - require.Failf(t, "unexpected type", "metrics.NewCounter() got %T; want %T", m, new(metrics.StandardCounter)) - case *metrics.StandardCounter: - default: - require.Failf(t, "unexpected type", "metrics.NewCounter() got unknown type %T", m) - } + require.IsType(t, (*metrics.StandardCounter)(nil), metrics.NewCounter(), "metrics.NewCounter() returned wrong type") } From 1ce92b94d3304b325b3b91b293684bcd6a75b51e Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:25:40 -0400 Subject: [PATCH 07/26] Update vms/evm/metrics/prometheus/prometheus_test.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/prometheus_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index fbed23040e6e..f4281a6af9ab 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -139,6 +139,8 @@ test_timer_count 6 } func registerNilMetrics(t *testing.T, register func(t *testing.T, name string, collector any)) { + // The NewXXX metrics functions return nil metrics types when the metrics + // are disabled. metrics.Enabled = false defer func() { metrics.Enabled = true }() nilCounter := metrics.NewCounter() From b82fcceaa42a098cd4c39bd81a65a4fd97de739d Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:26:13 -0400 Subject: [PATCH 08/26] Update vms/evm/metrics/prometheus/interfaces.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/interfaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/evm/metrics/prometheus/interfaces.go b/vms/evm/metrics/prometheus/interfaces.go index b8aa993453c0..dd5248281f9e 100644 --- a/vms/evm/metrics/prometheus/interfaces.go +++ b/vms/evm/metrics/prometheus/interfaces.go @@ -9,7 +9,7 @@ var _ Registry = metrics.Registry(nil) type Registry interface { // Call the given function for each registered metric. - Each(func(string, any)) + Each(func(name string, metric any)) // Get the metric by the given name or nil if none is registered. Get(string) any } From 32038aa3a3eca61e1f4ea0816f528bb08f9da8b0 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:26:32 -0400 Subject: [PATCH 09/26] Update vms/evm/metrics/prometheus/interfaces.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/interfaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/evm/metrics/prometheus/interfaces.go b/vms/evm/metrics/prometheus/interfaces.go index dd5248281f9e..4ddf117dd46a 100644 --- a/vms/evm/metrics/prometheus/interfaces.go +++ b/vms/evm/metrics/prometheus/interfaces.go @@ -11,5 +11,5 @@ type Registry interface { // Call the given function for each registered metric. Each(func(name string, metric any)) // Get the metric by the given name or nil if none is registered. - Get(string) any + Get(name string) any } From 6e7fced1f551b6243b5034f8333632a4ffae6ad3 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:27:19 -0400 Subject: [PATCH 10/26] Update vms/evm/metrics/prometheus/prometheus.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/prometheus.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index 0745e90509d2..343142fa1afa 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -15,8 +15,8 @@ import ( dto "github.com/prometheus/client_model/go" ) -// Gatherer implements [prometheus.Gatherer] interface by -// gathering all metrics from the given Prometheus registry. +// Gatherer implements the [prometheus.Gatherer] interface by gathering all +// metrics from a [Registry]. type Gatherer struct { registry Registry } From 7179722df90b11d8e62889e5aa766a0dcc1fae5d Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:28:14 -0400 Subject: [PATCH 11/26] Update vms/evm/metrics/prometheus/prometheus_test.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/prometheus_test.go | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index f4281a6af9ab..abe3acb6b5bd 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -143,28 +143,17 @@ func registerNilMetrics(t *testing.T, register func(t *testing.T, name string, c // are disabled. metrics.Enabled = false defer func() { metrics.Enabled = true }() - nilCounter := metrics.NewCounter() - register(t, "nil/counter", nilCounter) - nilCounterFloat64 := metrics.NewCounterFloat64() - register(t, "nil/counter_float64", nilCounterFloat64) - nilEWMA := &metrics.NilEWMA{} - register(t, "nil/ewma", nilEWMA) - nilGauge := metrics.NewGauge() - register(t, "nil/gauge", nilGauge) - nilGaugeFloat64 := metrics.NewGaugeFloat64() - register(t, "nil/gauge_float64", nilGaugeFloat64) - nilGaugeInfo := metrics.NewGaugeInfo() - register(t, "nil/gauge_info", nilGaugeInfo) - nilHealthcheck := metrics.NewHealthcheck(nil) - register(t, "nil/healthcheck", nilHealthcheck) - nilHistogram := metrics.NewHistogram(nil) - register(t, "nil/histogram", nilHistogram) - nilMeter := metrics.NewMeter() - register(t, "nil/meter", nilMeter) - nilResettingTimer := metrics.NewResettingTimer() - register(t, "nil/resetting_timer", nilResettingTimer) - nilSample := metrics.NewUniformSample(1028) - register(t, "nil/sample", nilSample) - nilTimer := metrics.NewTimer() - register(t, "nil/timer", nilTimer) + + register(t, "nil/counter", metrics.NewCounter()) + register(t, "nil/counter_float64", metrics.NewCounterFloat64()) + register(t, "nil/ewma", &metrics.NilEWMA{}) + register(t, "nil/gauge", metrics.NewGauge()) + register(t, "nil/gauge_float64", metrics.NewGaugeFloat64()) + register(t, "nil/gauge_info", metrics.NewGaugeInfo()) + register(t, "nil/healthcheck", metrics.NewHealthcheck(nil)) + register(t, "nil/histogram", metrics.NewHistogram(nil)) + register(t, "nil/meter", metrics.NewMeter()) + register(t, "nil/resetting_timer", metrics.NewResettingTimer()) + register(t, "nil/sample", metrics.NewUniformSample(1028)) + register(t, "nil/timer", metrics.NewTimer()) } From 6af4cd133767412aa46c1844f6bdc52c43a1d234 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:28:38 -0400 Subject: [PATCH 12/26] Update vms/evm/metrics/prometheus/prometheus_test.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/prometheus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index abe3acb6b5bd..36ddfa4e23fc 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -126,7 +126,7 @@ test_timer_count 6 require.Len(t, families, len(expectedMetrics)) for i, got := range families { - require.NotNil(t, *got.Name) + require.NotNil(t, got.Name) want := expectedMetrics[*got.Name] require.Equal(t, want, got, i) From 739310ce84740a0546d51b2eadbbf0a3e06a8ad9 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:39:50 -0400 Subject: [PATCH 13/26] Maintain initial metrics.Enabled value --- vms/evm/metrics/metricstest/metrics.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/vms/evm/metrics/metricstest/metrics.go b/vms/evm/metrics/metricstest/metrics.go index edd8a30ad29d..faf4d6686063 100644 --- a/vms/evm/metrics/metricstest/metrics.go +++ b/vms/evm/metrics/metricstest/metrics.go @@ -12,17 +12,19 @@ import ( var metricsLock sync.Mutex -// WithMetrics enables go-ethereum metrics globally for the test. -// If the [metrics.Enabled] is already true, nothing is done. -// Otherwise, it is set to true and is reverted to false when the test finishes. +// WithMetrics enables [metrics.Enabled] for the test and prevents any other +// tests with metrics from running concurrently. +// +// If [metrics.Enabled] is modified, its original value is restored as part of +// the testing cleanup. func WithMetrics(t testing.TB) { metricsLock.Lock() t.Cleanup(metricsLock.Unlock) - if metrics.Enabled { - return - } + initialValue := metrics.Enabled metrics.Enabled = true + + // Restore the original value t.Cleanup(func() { - metrics.Enabled = false + metrics.Enabled = initialValue }) } From c90b0d6f5f40fd0d5d3bc461117ef98923663518 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 10:58:41 -0400 Subject: [PATCH 14/26] Prometheus variable organization --- vms/evm/metrics/prometheus/enabled_test.go | 10 +++--- vms/evm/metrics/prometheus/prometheus.go | 41 +++++++++------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/vms/evm/metrics/prometheus/enabled_test.go b/vms/evm/metrics/prometheus/enabled_test.go index 0f772a655b7a..8e32e365378e 100644 --- a/vms/evm/metrics/prometheus/enabled_test.go +++ b/vms/evm/metrics/prometheus/enabled_test.go @@ -7,14 +7,14 @@ import ( "testing" "github.com/ava-labs/libevm/metrics" - // NOTE: This test assumes that there are no imported packages that might - // change the default value of [metrics.Enabled]. It is therefore in package - // `prometheus_test` in case any other tests modify the variable. If any - // imports here or in the implementation do actually do so then this test - // may have false negatives. "github.com/stretchr/testify/require" ) +// This test assumes that there are no imported packages that might change the +// default value of [metrics.Enabled]. It is therefore in package +// `prometheus_test` in case any other tests modify the variable. If any imports +// here or in the implementation do actually do so then this test may have false +// negatives. func TestMetricsEnabledByDefault(t *testing.T) { require.True(t, metrics.Enabled, "libevm/metrics.Enabled") require.IsType(t, (*metrics.StandardCounter)(nil), metrics.NewCounter(), "metrics.NewCounter() returned wrong type") diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index 343142fa1afa..3c91569ba055 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -15,21 +15,21 @@ import ( dto "github.com/prometheus/client_model/go" ) +var ( + _ prometheus.Gatherer = (*Gatherer)(nil) + + errMetricSkip = errors.New("metric skipped") + errMetricTypeNotSupported = errors.New("metric type is not supported") + quantiles = []float64{.5, .75, .95, .99, .999, .9999} + pvShortPercent = []float64{50, 95, 99} +) + // Gatherer implements the [prometheus.Gatherer] interface by gathering all // metrics from a [Registry]. type Gatherer struct { registry Registry } -var _ prometheus.Gatherer = (*Gatherer)(nil) - -// NewGatherer returns a [Gatherer] using the given registry. -func NewGatherer(registry Registry) *Gatherer { - return &Gatherer{ - registry: registry, - } -} - // Gather gathers metrics from the registry and converts them to // a slice of metric families. func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { @@ -55,12 +55,12 @@ func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { return mfs, nil } -var ( - errMetricSkip = errors.New("metric skipped") - errMetricTypeNotSupported = errors.New("metric type is not supported") -) - -func ptrTo[T any](x T) *T { return &x } +// NewGatherer returns a [Gatherer] using the given registry. +func NewGatherer(registry Registry) *Gatherer { + return &Gatherer{ + registry: registry, + } +} func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err error) { metric := registry.Get(name) @@ -116,8 +116,6 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err return nil, fmt.Errorf("%w: %q is a %T", errMetricSkip, name, m) case metrics.Histogram: snapshot := m.Snapshot() - - quantiles := []float64{.5, .75, .95, .99, .999, .9999} thresholds := snapshot.Percentiles(quantiles) dtoQuantiles := make([]*dto.Quantile, len(quantiles)) for i := range thresholds { @@ -126,7 +124,6 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err Value: ptrTo(thresholds[i]), } } - return &dto.MetricFamily{ Name: &name, Type: dto.MetricType_SUMMARY.Enum(), @@ -150,8 +147,6 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err }, nil case metrics.Timer: snapshot := m.Snapshot() - - quantiles := []float64{.5, .75, .95, .99, .999, .9999} thresholds := snapshot.Percentiles(quantiles) dtoQuantiles := make([]*dto.Quantile, len(quantiles)) for i := range thresholds { @@ -160,7 +155,6 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err Value: ptrTo(thresholds[i]), } } - return &dto.MetricFamily{ Name: &name, Type: dto.MetricType_SUMMARY.Enum(), @@ -178,8 +172,6 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err if count == 0 { return nil, fmt.Errorf("%w: %q resetting timer metric count is zero", errMetricSkip, name) } - - pvShortPercent := []float64{50, 95, 99} thresholds := snapshot.Percentiles(pvShortPercent) dtoQuantiles := make([]*dto.Quantile, len(pvShortPercent)) for i := range pvShortPercent { @@ -188,7 +180,6 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err Value: ptrTo(thresholds[i]), } } - return &dto.MetricFamily{ Name: &name, Type: dto.MetricType_SUMMARY.Enum(), @@ -204,3 +195,5 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err return nil, fmt.Errorf("%w: metric %q type %T", errMetricTypeNotSupported, name, metric) } } + +func ptrTo[T any](x T) *T { return &x } From c6b8a185f7f7d8549575a4b3c2f5f915a75060ae Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 11:10:35 -0400 Subject: [PATCH 15/26] Refactor vms/evm/metrics/prometheus/prometheus_test.go --- vms/evm/metrics/prometheus/prometheus_test.go | 136 ++++++++++-------- 1 file changed, 73 insertions(+), 63 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index 36ddfa4e23fc..c518062be197 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/ava-labs/libevm/metrics" + dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "github.com/stretchr/testify/require" @@ -25,6 +26,78 @@ func TestGatherer_Gather(t *testing.T) { require.NoErrorf(t, err, "registering collector %q", name) } + registerNilMetrics(t, register) + registerRealMetrics(t, register) + + gatherer := NewGatherer(registry) + + families, err := gatherer.Gather() + require.NoError(t, err) + + expectedMetrics := expectedMetrics(t) + require.Len(t, families, len(expectedMetrics)) + for i, got := range families { + require.NotNil(t, got.Name) + + want := expectedMetrics[*got.Name] + require.Equal(t, want, got, i) + } + + register(t, "unsupported", metrics.NewHealthcheck(nil)) + families, err = gatherer.Gather() + require.ErrorIs(t, err, errMetricTypeNotSupported) + require.Empty(t, families) +} + +func expectedMetrics(t *testing.T) map[string]*dto.MetricFamily { + const expectedString = ` + # TYPE test_counter counter + test_counter 12345 + # TYPE test_counter_float64 counter + test_counter_float64 1.1 + # TYPE test_gauge gauge + test_gauge 23456 + # TYPE test_gauge_float64 gauge + test_gauge_float64 34567.89 + # TYPE test_histogram summary + test_histogram{quantile="0.5"} 0 + test_histogram{quantile="0.75"} 0 + test_histogram{quantile="0.95"} 0 + test_histogram{quantile="0.99"} 0 + test_histogram{quantile="0.999"} 0 + test_histogram{quantile="0.9999"} 0 + test_histogram_sum 0 + test_histogram_count 0 + # TYPE test_meter gauge + test_meter 9.999999e+06 + # TYPE test_resetting_timer summary + test_resetting_timer{quantile="50"} 1e+09 + test_resetting_timer{quantile="95"} 1e+09 + test_resetting_timer{quantile="99"} 1e+09 + test_resetting_timer_sum 1e+09 + test_resetting_timer_count 1 + # TYPE test_timer summary + test_timer{quantile="0.5"} 2.25e+07 + test_timer{quantile="0.75"} 4.8e+07 + test_timer{quantile="0.95"} 1.2e+08 + test_timer{quantile="0.99"} 1.2e+08 + test_timer{quantile="0.999"} 1.2e+08 + test_timer{quantile="0.9999"} 1.2e+08 + test_timer_sum 2.3e+08 + test_timer_count 6 + ` + + var ( + stringReader = strings.NewReader(expectedString) + parser expfmt.TextParser + ) + + expectedMetrics, err := parser.TextToMetricFamilies(stringReader) + require.NoError(t, err) + return expectedMetrics +} + +func registerRealMetrics(t *testing.T, register func(t *testing.T, name string, collector any)) { counter := metrics.NewCounter() counter.Inc(12345) register(t, "test/counter", counter) @@ -73,69 +146,6 @@ func TestGatherer_Gather(t *testing.T) { emptyResettingTimer.Update(time.Second) // no effect because of snapshot below register(t, "test/empty_resetting_timer_snapshot", emptyResettingTimer.Snapshot()) - - registerNilMetrics(t, register) - - gatherer := NewGatherer(registry) - - families, err := gatherer.Gather() - require.NoError(t, err) - - const expectedString = ` -# TYPE test_counter counter -test_counter 12345 -# TYPE test_counter_float64 counter -test_counter_float64 1.1 -# TYPE test_gauge gauge -test_gauge 23456 -# TYPE test_gauge_float64 gauge -test_gauge_float64 34567.89 -# TYPE test_histogram summary -test_histogram{quantile="0.5"} 0 -test_histogram{quantile="0.75"} 0 -test_histogram{quantile="0.95"} 0 -test_histogram{quantile="0.99"} 0 -test_histogram{quantile="0.999"} 0 -test_histogram{quantile="0.9999"} 0 -test_histogram_sum 0 -test_histogram_count 0 -# TYPE test_meter gauge -test_meter 9.999999e+06 -# TYPE test_resetting_timer summary -test_resetting_timer{quantile="50"} 1e+09 -test_resetting_timer{quantile="95"} 1e+09 -test_resetting_timer{quantile="99"} 1e+09 -test_resetting_timer_sum 1e+09 -test_resetting_timer_count 1 -# TYPE test_timer summary -test_timer{quantile="0.5"} 2.25e+07 -test_timer{quantile="0.75"} 4.8e+07 -test_timer{quantile="0.95"} 1.2e+08 -test_timer{quantile="0.99"} 1.2e+08 -test_timer{quantile="0.999"} 1.2e+08 -test_timer{quantile="0.9999"} 1.2e+08 -test_timer_sum 2.3e+08 -test_timer_count 6 -` - var ( - stringReader = strings.NewReader(expectedString) - parser expfmt.TextParser - ) - expectedMetrics, err := parser.TextToMetricFamilies(stringReader) - require.NoError(t, err) - - require.Len(t, families, len(expectedMetrics)) - for i, got := range families { - require.NotNil(t, got.Name) - - want := expectedMetrics[*got.Name] - require.Equal(t, want, got, i) - } - - register(t, "unsupported", metrics.NewHealthcheck(nil)) - families, err = gatherer.Gather() - require.ErrorIs(t, err, errMetricTypeNotSupported) - require.Empty(t, families) } func registerNilMetrics(t *testing.T, register func(t *testing.T, name string, collector any)) { From 0999f21b96b8c45ee974053d3f062c7a80b475c7 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Wed, 30 Jul 2025 11:15:06 -0400 Subject: [PATCH 16/26] Accumulate errors in Gather() --- vms/evm/metrics/prometheus/prometheus.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index 3c91569ba055..6306c4328e12 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -35,6 +35,7 @@ type Gatherer struct { func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { // Gather and pre-sort the metrics to avoid random listings var names []string + var errs []error g.registry.Each(func(name string, _ any) { names = append(names, name) }) @@ -43,16 +44,17 @@ func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { mfs = make([]*dto.MetricFamily, 0, len(names)) for _, name := range names { mf, err := metricFamily(g.registry, name) + switch { case errors.Is(err, errMetricSkip): continue case err != nil: - return nil, err + errs = append(errs, err) } mfs = append(mfs, mf) } - return mfs, nil + return mfs, errors.Join(errs...) } // NewGatherer returns a [Gatherer] using the given registry. From 8af978f7ae09495e266b9f011e3eb010406d2f1b Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Thu, 31 Jul 2025 11:47:39 -0400 Subject: [PATCH 17/26] Proper import order --- vms/evm/metrics/prometheus/prometheus_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index c518062be197..afb2151b25ea 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -9,11 +9,12 @@ import ( "time" "github.com/ava-labs/libevm/metrics" - dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/vms/evm/metrics/metricstest" + + dto "github.com/prometheus/client_model/go" ) func TestGatherer_Gather(t *testing.T) { From 83290c2350d8a5d3631ef44e5671f73e80635299 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Thu, 31 Jul 2025 12:02:41 -0400 Subject: [PATCH 18/26] Add continue to swtich case --- vms/evm/metrics/prometheus/prometheus.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index 6306c4328e12..e37723ab417f 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -50,11 +50,15 @@ func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { continue case err != nil: errs = append(errs, err) + continue } mfs = append(mfs, mf) } - return mfs, errors.Join(errs...) + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + return mfs, nil } // NewGatherer returns a [Gatherer] using the given registry. From 19d4194c4d39d2fa4e1905707f691ec39e63461e Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Thu, 31 Jul 2025 16:58:02 -0400 Subject: [PATCH 19/26] Update vms/evm/metrics/metricstest/metrics.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/metricstest/metrics.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vms/evm/metrics/metricstest/metrics.go b/vms/evm/metrics/metricstest/metrics.go index faf4d6686063..0ec0dd2cfefd 100644 --- a/vms/evm/metrics/metricstest/metrics.go +++ b/vms/evm/metrics/metricstest/metrics.go @@ -15,8 +15,7 @@ var metricsLock sync.Mutex // WithMetrics enables [metrics.Enabled] for the test and prevents any other // tests with metrics from running concurrently. // -// If [metrics.Enabled] is modified, its original value is restored as part of -// the testing cleanup. +// [metrics.Enabled] is restored to its original value during testing cleanup. func WithMetrics(t testing.TB) { metricsLock.Lock() t.Cleanup(metricsLock.Unlock) From 8980eb1b22c70ac3960e767a6f04edcb8ecbb960 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Thu, 31 Jul 2025 16:58:08 -0400 Subject: [PATCH 20/26] Update vms/evm/metrics/prometheus/prometheus.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/prometheus.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index e37723ab417f..fe216920a0fd 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -44,15 +44,12 @@ func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { mfs = make([]*dto.MetricFamily, 0, len(names)) for _, name := range names { mf, err := metricFamily(g.registry, name) - switch { - case errors.Is(err, errMetricSkip): - continue - case err != nil: + case err == nil: + mfs = append(mfs, mf) + case !errors.Is(err, errMetricSkip): errs = append(errs, err) - continue } - mfs = append(mfs, mf) } if len(errs) > 0 { From 2e5d18a0868e36d5c12368f349e56cdf545001c4 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Thu, 31 Jul 2025 16:58:22 -0400 Subject: [PATCH 21/26] Update vms/evm/metrics/prometheus/prometheus.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/prometheus.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index fe216920a0fd..436df5f42c61 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -35,13 +35,15 @@ type Gatherer struct { func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { // Gather and pre-sort the metrics to avoid random listings var names []string - var errs []error g.registry.Each(func(name string, _ any) { names = append(names, name) }) slices.Sort(names) - mfs = make([]*dto.MetricFamily, 0, len(names)) + var ( + mfs = make([]*dto.MetricFamily, 0, len(names)) + errs []error + ) for _, name := range names { mf, err := metricFamily(g.registry, name) switch { From 149e8c286f2e62f93adbf5e5236378fc08e4a424 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Thu, 31 Jul 2025 16:58:39 -0400 Subject: [PATCH 22/26] Update vms/evm/metrics/prometheus/prometheus.go Co-authored-by: Stephen Buttolph Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> --- vms/evm/metrics/prometheus/prometheus.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index 436df5f42c61..d88b5fb6298b 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -54,10 +54,7 @@ func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { } } - if len(errs) > 0 { - return nil, errors.Join(errs...) - } - return mfs, nil + return mfs, errors.Join(errs...) } // NewGatherer returns a [Gatherer] using the given registry. From 36b98e99ddfdfc907c15ff559b4e131b7e6f0f02 Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Thu, 31 Jul 2025 17:17:28 -0400 Subject: [PATCH 23/26] Fix TestGatherer_Gather test case --- vms/evm/metrics/prometheus/prometheus.go | 5 +-- vms/evm/metrics/prometheus/prometheus_test.go | 36 +++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index d88b5fb6298b..706485d0b990 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -40,10 +40,7 @@ func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { }) slices.Sort(names) - var ( - mfs = make([]*dto.MetricFamily, 0, len(names)) - errs []error - ) + var errs []error for _, name := range names { mf, err := metricFamily(g.registry, name) switch { diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index afb2151b25ea..47a9a5112062 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -32,22 +32,46 @@ func TestGatherer_Gather(t *testing.T) { gatherer := NewGatherer(registry) + // Test successful gathering families, err := gatherer.Gather() require.NoError(t, err) expectedMetrics := expectedMetrics(t) - require.Len(t, families, len(expectedMetrics)) - for i, got := range families { - require.NotNil(t, got.Name) - want := expectedMetrics[*got.Name] - require.Equal(t, want, got, i) + require.Equal(t, len(expectedMetrics), len(families), "Expected %d metrics, got %d", len(expectedMetrics), len(families)) + + // Compare metrics by name since they're sorted alphabetically + for _, got := range families { + require.NotNil(t, got.Name) + want, exists := expectedMetrics[*got.Name] + require.True(t, exists, "unexpected metric: %s", *got.Name) + require.Equal(t, want, got, "metric: %s", *got.Name) } + // Test gathering with unsupported metric type register(t, "unsupported", metrics.NewHealthcheck(nil)) families, err = gatherer.Gather() require.ErrorIs(t, err, errMetricTypeNotSupported) - require.Empty(t, families) + + // When there's an error, we should still get the valid metrics that were gathered before the error + // ResettingTimer metrics reset after being read, so test_resetting_timer won't be included in + // subsequent Gather() calls + expectedMetricsWithoutResettingTimer := make(map[string]*dto.MetricFamily) + for name, metric := range expectedMetrics { + if name != "test_resetting_timer" { + expectedMetricsWithoutResettingTimer[name] = metric + } + } + + require.Len(t, families, len(expectedMetricsWithoutResettingTimer)) + + // Compare metrics by name since they're sorted alphabetically + for _, got := range families { + require.NotNil(t, got.Name) + want, exists := expectedMetricsWithoutResettingTimer[*got.Name] + require.True(t, exists, "unexpected metric: %s", *got.Name) + require.Equal(t, want, got, "metric: %s", *got.Name) + } } func expectedMetrics(t *testing.T) map[string]*dto.MetricFamily { From 0a4e60ac63a100242a95fff75bc03a445b7b62ca Mon Sep 17 00:00:00 2001 From: Jonathan Oppenheimer Date: Thu, 31 Jul 2025 17:46:48 -0400 Subject: [PATCH 24/26] lint error --- vms/evm/metrics/prometheus/prometheus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index 47a9a5112062..3259498ce5ba 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -38,7 +38,7 @@ func TestGatherer_Gather(t *testing.T) { expectedMetrics := expectedMetrics(t) - require.Equal(t, len(expectedMetrics), len(families), "Expected %d metrics, got %d", len(expectedMetrics), len(families)) + require.Len(t, families, len(expectedMetrics)) // Compare metrics by name since they're sorted alphabetically for _, got := range families { From 2ce2d42b7430697bce6734c30e58b98a8fd051d0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Jul 2025 18:38:37 -0400 Subject: [PATCH 25/26] Address final nits (#4148) --- vms/evm/metrics/metricstest/metrics.go | 4 +- vms/evm/metrics/prometheus/prometheus.go | 7 +- vms/evm/metrics/prometheus/prometheus_test.go | 135 +++++++----------- 3 files changed, 55 insertions(+), 91 deletions(-) diff --git a/vms/evm/metrics/metricstest/metrics.go b/vms/evm/metrics/metricstest/metrics.go index 0ec0dd2cfefd..6d21ada56db5 100644 --- a/vms/evm/metrics/metricstest/metrics.go +++ b/vms/evm/metrics/metricstest/metrics.go @@ -18,12 +18,10 @@ var metricsLock sync.Mutex // [metrics.Enabled] is restored to its original value during testing cleanup. func WithMetrics(t testing.TB) { metricsLock.Lock() - t.Cleanup(metricsLock.Unlock) initialValue := metrics.Enabled metrics.Enabled = true - - // Restore the original value t.Cleanup(func() { metrics.Enabled = initialValue + metricsLock.Unlock() }) } diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index 706485d0b990..b3685607af05 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -32,7 +32,7 @@ type Gatherer struct { // Gather gathers metrics from the registry and converts them to // a slice of metric families. -func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { +func (g *Gatherer) Gather() ([]*dto.MetricFamily, error) { // Gather and pre-sort the metrics to avoid random listings var names []string g.registry.Each(func(name string, _ any) { @@ -40,7 +40,10 @@ func (g *Gatherer) Gather() (mfs []*dto.MetricFamily, err error) { }) slices.Sort(names) - var errs []error + var ( + mfs = make([]*dto.MetricFamily, 0, len(names)) + errs []error + ) for _, name := range names { mf, err := metricFamily(g.registry, name) switch { diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index 3259498ce5ba..d29a02700289 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -9,14 +9,49 @@ import ( "time" "github.com/ava-labs/libevm/metrics" - "github.com/prometheus/common/expfmt" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/vms/evm/metrics/metricstest" - - dto "github.com/prometheus/client_model/go" ) +const expectedMetrics = ` + # TYPE test_counter counter + test_counter 12345 + # TYPE test_counter_float64 counter + test_counter_float64 1.1 + # TYPE test_gauge gauge + test_gauge 23456 + # TYPE test_gauge_float64 gauge + test_gauge_float64 34567.89 + # TYPE test_histogram summary + test_histogram{quantile="0.5"} 0 + test_histogram{quantile="0.75"} 0 + test_histogram{quantile="0.95"} 0 + test_histogram{quantile="0.99"} 0 + test_histogram{quantile="0.999"} 0 + test_histogram{quantile="0.9999"} 0 + test_histogram_sum 0 + test_histogram_count 0 + # TYPE test_meter gauge + test_meter 9.999999e+06 + # TYPE test_resetting_timer summary + test_resetting_timer{quantile="50"} 1e+09 + test_resetting_timer{quantile="95"} 1e+09 + test_resetting_timer{quantile="99"} 1e+09 + test_resetting_timer_sum 1e+09 + test_resetting_timer_count 1 + # TYPE test_timer summary + test_timer{quantile="0.5"} 2.25e+07 + test_timer{quantile="0.75"} 4.8e+07 + test_timer{quantile="0.95"} 1.2e+08 + test_timer{quantile="0.99"} 1.2e+08 + test_timer{quantile="0.999"} 1.2e+08 + test_timer{quantile="0.9999"} 1.2e+08 + test_timer_sum 2.3e+08 + test_timer_count 6 +` + func TestGatherer_Gather(t *testing.T) { metricstest.WithMetrics(t) @@ -32,94 +67,22 @@ func TestGatherer_Gather(t *testing.T) { gatherer := NewGatherer(registry) - // Test successful gathering - families, err := gatherer.Gather() - require.NoError(t, err) - - expectedMetrics := expectedMetrics(t) + // Test successful gathering. + // + // TODO: This results in resetting the timer, is this expected behavior? + require.NoError(t, testutil.GatherAndCompare( + gatherer, + strings.NewReader(expectedMetrics), + )) - require.Len(t, families, len(expectedMetrics)) - - // Compare metrics by name since they're sorted alphabetically - for _, got := range families { - require.NotNil(t, got.Name) - want, exists := expectedMetrics[*got.Name] - require.True(t, exists, "unexpected metric: %s", *got.Name) - require.Equal(t, want, got, "metric: %s", *got.Name) - } + wantMetrics, err := gatherer.Gather() + require.NoError(t, err) // Test gathering with unsupported metric type register(t, "unsupported", metrics.NewHealthcheck(nil)) - families, err = gatherer.Gather() + metrics, err := gatherer.Gather() require.ErrorIs(t, err, errMetricTypeNotSupported) - - // When there's an error, we should still get the valid metrics that were gathered before the error - // ResettingTimer metrics reset after being read, so test_resetting_timer won't be included in - // subsequent Gather() calls - expectedMetricsWithoutResettingTimer := make(map[string]*dto.MetricFamily) - for name, metric := range expectedMetrics { - if name != "test_resetting_timer" { - expectedMetricsWithoutResettingTimer[name] = metric - } - } - - require.Len(t, families, len(expectedMetricsWithoutResettingTimer)) - - // Compare metrics by name since they're sorted alphabetically - for _, got := range families { - require.NotNil(t, got.Name) - want, exists := expectedMetricsWithoutResettingTimer[*got.Name] - require.True(t, exists, "unexpected metric: %s", *got.Name) - require.Equal(t, want, got, "metric: %s", *got.Name) - } -} - -func expectedMetrics(t *testing.T) map[string]*dto.MetricFamily { - const expectedString = ` - # TYPE test_counter counter - test_counter 12345 - # TYPE test_counter_float64 counter - test_counter_float64 1.1 - # TYPE test_gauge gauge - test_gauge 23456 - # TYPE test_gauge_float64 gauge - test_gauge_float64 34567.89 - # TYPE test_histogram summary - test_histogram{quantile="0.5"} 0 - test_histogram{quantile="0.75"} 0 - test_histogram{quantile="0.95"} 0 - test_histogram{quantile="0.99"} 0 - test_histogram{quantile="0.999"} 0 - test_histogram{quantile="0.9999"} 0 - test_histogram_sum 0 - test_histogram_count 0 - # TYPE test_meter gauge - test_meter 9.999999e+06 - # TYPE test_resetting_timer summary - test_resetting_timer{quantile="50"} 1e+09 - test_resetting_timer{quantile="95"} 1e+09 - test_resetting_timer{quantile="99"} 1e+09 - test_resetting_timer_sum 1e+09 - test_resetting_timer_count 1 - # TYPE test_timer summary - test_timer{quantile="0.5"} 2.25e+07 - test_timer{quantile="0.75"} 4.8e+07 - test_timer{quantile="0.95"} 1.2e+08 - test_timer{quantile="0.99"} 1.2e+08 - test_timer{quantile="0.999"} 1.2e+08 - test_timer{quantile="0.9999"} 1.2e+08 - test_timer_sum 2.3e+08 - test_timer_count 6 - ` - - var ( - stringReader = strings.NewReader(expectedString) - parser expfmt.TextParser - ) - - expectedMetrics, err := parser.TextToMetricFamilies(stringReader) - require.NoError(t, err) - return expectedMetrics + require.Equal(t, wantMetrics, metrics) } func registerRealMetrics(t *testing.T, register func(t *testing.T, name string, collector any)) { From 22ea45039985b69ba13a2971761d213dc22c343c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Jul 2025 18:50:46 -0400 Subject: [PATCH 26/26] Remove weird edge case --- vms/evm/metrics/prometheus/prometheus.go | 5 +---- vms/evm/metrics/prometheus/prometheus_test.go | 6 ++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/vms/evm/metrics/prometheus/prometheus.go b/vms/evm/metrics/prometheus/prometheus.go index b3685607af05..99b495f5ec01 100644 --- a/vms/evm/metrics/prometheus/prometheus.go +++ b/vms/evm/metrics/prometheus/prometheus.go @@ -170,10 +170,6 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err }, nil case metrics.ResettingTimer: snapshot := m.Snapshot() - count := snapshot.Count() - if count == 0 { - return nil, fmt.Errorf("%w: %q resetting timer metric count is zero", errMetricSkip, name) - } thresholds := snapshot.Percentiles(pvShortPercent) dtoQuantiles := make([]*dto.Quantile, len(pvShortPercent)) for i := range pvShortPercent { @@ -182,6 +178,7 @@ func metricFamily(registry Registry, name string) (mf *dto.MetricFamily, err err Value: ptrTo(thresholds[i]), } } + count := snapshot.Count() return &dto.MetricFamily{ Name: &name, Type: dto.MetricType_SUMMARY.Enum(), diff --git a/vms/evm/metrics/prometheus/prometheus_test.go b/vms/evm/metrics/prometheus/prometheus_test.go index d29a02700289..8b65a5c3606c 100644 --- a/vms/evm/metrics/prometheus/prometheus_test.go +++ b/vms/evm/metrics/prometheus/prometheus_test.go @@ -20,6 +20,12 @@ const expectedMetrics = ` test_counter 12345 # TYPE test_counter_float64 counter test_counter_float64 1.1 + # TYPE test_empty_resetting_timer summary + test_empty_resetting_timer{quantile="50"} 0 + test_empty_resetting_timer{quantile="95"} 0 + test_empty_resetting_timer{quantile="99"} 0 + test_empty_resetting_timer_sum 0 + test_empty_resetting_timer_count 0 # TYPE test_gauge gauge test_gauge 23456 # TYPE test_gauge_float64 gauge