-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate subnet-evm specific files back to metrics/prometheus
- Bring over refactoring and fixes done in ava-labs/libevm#103 - Bring over test refactoring done in ava-labs/libevm#103
- Loading branch information
Showing
4 changed files
with
288 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// (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) { | ||
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:<counter:<value:12345 > > `, | ||
`name:"test_gauge" type:GAUGE metric:<gauge:<value:23456 > > `, | ||
`name:"test_gauge_float64" type:GAUGE metric:<gauge:<value:34567.89 > > `, | ||
`name:"test_histogram" type:SUMMARY metric:<summary:<sample_count:0 sample_sum:0 quantile:<quantile:0.5 value:0 > quantile:<quantile:0.75 value:0 > quantile:<quantile:0.95 value:0 > quantile:<quantile:0.99 value:0 > quantile:<quantile:0.999 value:0 > quantile:<quantile:0.9999 value:0 > > > `, | ||
`name:"test_meter" type:GAUGE metric:<gauge:<value:9.999999e+06 > > `, | ||
`name:"test_resetting_timer" type:SUMMARY metric:<summary:<sample_count:1 quantile:<quantile:50 value:1e+09 > quantile:<quantile:95 value:1e+09 > quantile:<quantile:99 value:1e+09 > > > `, | ||
`name:"test_timer" type:SUMMARY metric:<summary:<sample_count:6 sample_sum:2.3e+08 quantile:<quantile:0.5 value:2.25e+07 > quantile:<quantile:0.75 value:4.8e+07 > quantile:<quantile:0.95 value:1.2e+08 > quantile:<quantile:0.99 value:1.2e+08 > quantile:<quantile:0.999 value:1.2e+08 > quantile:<quantile:0.9999 value:1.2e+08 > > > `, | ||
} | ||
assert.Equal(t, want, familyStrings) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters