From 8c8514c8cbb1163ad38211d7cf811594e32163df Mon Sep 17 00:00:00 2001 From: Quentin Mc Gaw Date: Mon, 13 Jan 2025 15:42:19 +0100 Subject: [PATCH] Migrate subnet-evm specific files back to metrics/prometheus - Bring over refactoring and fixes done in https://github.com/ava-labs/libevm/pull/103 - Bring over test refactoring done in https://github.com/ava-labs/libevm/pull/103 --- go.mod | 4 +- go.sum | 4 +- metrics/prometheus/interfaces.go | 10 ++ metrics/prometheus/prometheus.go | 193 ++++++++++++++++++++++++++ metrics/prometheus/prometheus_test.go | 91 ++++++++++++ plugin/evm/vm.go | 4 +- 6 files changed, 300 insertions(+), 6 deletions(-) create mode 100644 metrics/prometheus/interfaces.go create mode 100644 metrics/prometheus/prometheus.go create mode 100644 metrics/prometheus/prometheus_test.go diff --git a/go.mod b/go.mod index c214e6a1c5..4ffb2e25ea 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 github.com/ava-labs/avalanchego v1.12.1 - github.com/ava-labs/libevm v0.0.0-20250113110843-18c93de8be7f + github.com/ava-labs/libevm v1.13.14-0.1.0-rc.1 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -31,6 +31,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo/v2 v2.13.1 github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_model v0.3.0 github.com/spf13/cast v1.5.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 @@ -128,7 +129,6 @@ require ( github.com/pires/go-proxyproto v0.6.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect diff --git a/go.sum b/go.sum index 2e61401a83..469ca3d9b6 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,8 @@ github.com/ava-labs/avalanchego v1.12.1 h1:NL04K5+gciC2XqGZbDcIu0nuVApEddzc6Yyuj github.com/ava-labs/avalanchego v1.12.1/go.mod h1:xnVvN86jhxndxfS8e0U7v/0woyfx9BhX/feld7XDjDE= github.com/ava-labs/coreth v0.13.9-rc.2-encapsulate-signer h1:mRB03tLPUvgNko4nP4VwWQdiHeHaLHtdwsnqwxrsGec= github.com/ava-labs/coreth v0.13.9-rc.2-encapsulate-signer/go.mod h1:tqRAe+7bGLo2Rq/Ph4iYMSch72ag/Jn0DiDMDz1Xa9E= -github.com/ava-labs/libevm v0.0.0-20250113110843-18c93de8be7f h1:KeKggoIyyF+o/GeGofo2+UO93WN7ulqMcVlP2K3iUzM= -github.com/ava-labs/libevm v0.0.0-20250113110843-18c93de8be7f/go.mod h1:M8TCw2g1D5GBB7hu7g1F4aot5bRHGSxnBawNVmHE9Z0= +github.com/ava-labs/libevm v1.13.14-0.1.0-rc.1 h1:ughW0E2DUNRnvwJYNU8zUSCUzIWdcOwyXSBpy7oauZE= +github.com/ava-labs/libevm v1.13.14-0.1.0-rc.1/go.mod h1:yBctIV/wnxXTF38h95943jvpuk4aj07TrjbpoGor6LQ= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= diff --git a/metrics/prometheus/interfaces.go b/metrics/prometheus/interfaces.go new file mode 100644 index 0000000000..234627d862 --- /dev/null +++ b/metrics/prometheus/interfaces.go @@ -0,0 +1,10 @@ +// (c) 2025 Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package prometheus + +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/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go new file mode 100644 index 0000000000..1061921da7 --- /dev/null +++ b/metrics/prometheus/prometheus.go @@ -0,0 +1,193 @@ +// (c) 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" +) + +type Gatherer struct { + registry Registry +} + +var _ prometheus.Gatherer = (*Gatherer)(nil) + +// NewGatherer returns a gatherer using the given registry. +// Note this gatherer implements the [prometheus.Gatherer] interface. +func NewGatherer(registry Registry) *Gatherer { + return &Gatherer{ + registry: registry, + } +} + +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 errors.Is(err, errMetricSkip) { + continue + } + mfs = append(mfs, mf) + } + + return mfs, nil +} + +var ( + errMetricSkip = errors.New("metric skipped") +) + +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.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() + if snapshot.Count() == 0 { + return nil, fmt.Errorf("%w: resetting timer metric count is zero", errMetricSkip) + } + + 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(snapshot.Count())), //nolint:gosec + // TODO: do we need to specify SampleSum here? and if so + // what should that be? + Quantile: dtoQuantiles, + }, + }}, + }, nil + default: + return nil, fmt.Errorf("metric type is not supported: %T", metric) + } +} diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go new file mode 100644 index 0000000000..4eb7eb994a --- /dev/null +++ b/metrics/prometheus/prometheus_test.go @@ -0,0 +1,91 @@ +// (c) 2025 Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package prometheus + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/libevm/metrics" +) + +func TestGatherer_Gather(t *testing.T) { + metricsEnabled := metrics.Enabled + if !metricsEnabled { + metrics.Enabled = true + t.Cleanup(func() { + metrics.Enabled = false + }) + } + + registry := metrics.NewRegistry() + register := func(t *testing.T, name string, collector any) { + t.Helper() + err := registry.Register(name, collector) + require.NoError(t, err) + } + + counter := metrics.NewCounter() + counter.Inc(12345) + register(t, "test/counter", counter) + + gauge := metrics.NewGauge() + gauge.Update(23456) + register(t, "test/gauge", gauge) + + gaugeFloat64 := metrics.NewGaugeFloat64() + gaugeFloat64.Update(34567.89) + register(t, "test/gauge_float64", gaugeFloat64) + + 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()) + + g := NewGatherer(registry) + + families, err := g.Gather() + require.NoError(t, err) + familyStrings := make([]string, len(families)) + for i := range families { + familyStrings[i] = families[i].String() + } + want := []string{ + `name:"test_counter" type:COUNTER metric: > `, + `name:"test_gauge" type:GAUGE metric: > `, + `name:"test_gauge_float64" type:GAUGE metric: > `, + `name:"test_histogram" type:SUMMARY metric: quantile: quantile: quantile: quantile: quantile: > > `, + `name:"test_meter" type:GAUGE metric: > `, + `name:"test_resetting_timer" type:SUMMARY metric: quantile: quantile: > > `, + `name:"test_timer" type:SUMMARY metric: quantile: quantile: quantile: quantile: quantile: > > `, + } + assert.Equal(t, want, familyStrings) +} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 7c419edb6d..1edd6d305e 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -24,7 +24,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/libevm/metrics" - libevmprometheus "github.com/ava-labs/libevm/metrics/prometheus" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/constants" @@ -34,6 +33,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/eth" "github.com/ava-labs/subnet-evm/eth/ethconfig" + subnetevmprometheus "github.com/ava-labs/subnet-evm/metrics/prometheus" "github.com/ava-labs/subnet-evm/miner" "github.com/ava-labs/subnet-evm/node" "github.com/ava-labs/subnet-evm/params" @@ -555,7 +555,7 @@ func (vm *VM) initializeMetrics() error { return nil } - gatherer := libevmprometheus.NewGatherer(metrics.DefaultRegistry) + gatherer := subnetevmprometheus.NewGatherer(metrics.DefaultRegistry) if err := vm.ctx.Metrics.Register(ethMetricsPrefix, gatherer); err != nil { return err }