Commit 6096d317 authored by refcell's avatar refcell Committed by GitHub

feat(op-dispute-mon): output fetch time metric (#9744)

parent 5928a728
...@@ -38,6 +38,8 @@ type Metricer interface { ...@@ -38,6 +38,8 @@ type Metricer interface {
RecordClaimResolutionDelayMax(delay float64) RecordClaimResolutionDelayMax(delay float64)
RecordOutputFetchTime(timestamp float64)
RecordGamesStatus(inProgress, defenderWon, challengerWon int) RecordGamesStatus(inProgress, defenderWon, challengerWon int)
RecordGameAgreement(status GameAgreementStatus, count int) RecordGameAgreement(status GameAgreementStatus, count int)
...@@ -57,6 +59,8 @@ type Metrics struct { ...@@ -57,6 +59,8 @@ type Metrics struct {
info prometheus.GaugeVec info prometheus.GaugeVec
up prometheus.Gauge up prometheus.Gauge
lastOutputFetch prometheus.Gauge
claimResolutionDelayMax prometheus.Gauge claimResolutionDelayMax prometheus.Gauge
trackedGames prometheus.GaugeVec trackedGames prometheus.GaugeVec
...@@ -92,6 +96,11 @@ func NewMetrics() *Metrics { ...@@ -92,6 +96,11 @@ func NewMetrics() *Metrics {
Name: "up", Name: "up",
Help: "1 if the op-challenger has finished starting up", Help: "1 if the op-challenger has finished starting up",
}), }),
lastOutputFetch: factory.NewGauge(prometheus.GaugeOpts{
Namespace: Namespace,
Name: "last_output_fetch",
Help: "Timestamp of the last output fetch",
}),
claimResolutionDelayMax: factory.NewGauge(prometheus.GaugeOpts{ claimResolutionDelayMax: factory.NewGauge(prometheus.GaugeOpts{
Namespace: Namespace, Namespace: Namespace,
Name: "claim_resolution_delay_max", Name: "claim_resolution_delay_max",
...@@ -155,6 +164,10 @@ func (m *Metrics) RecordGamesStatus(inProgress, defenderWon, challengerWon int) ...@@ -155,6 +164,10 @@ func (m *Metrics) RecordGamesStatus(inProgress, defenderWon, challengerWon int)
m.trackedGames.WithLabelValues("challenger_won").Set(float64(challengerWon)) m.trackedGames.WithLabelValues("challenger_won").Set(float64(challengerWon))
} }
func (m *Metrics) RecordOutputFetchTime(timestamp float64) {
m.lastOutputFetch.Set(timestamp)
}
func (m *Metrics) RecordGameAgreement(status GameAgreementStatus, count int) { func (m *Metrics) RecordGameAgreement(status GameAgreementStatus, count int) {
m.gamesAgreement.WithLabelValues(labelValuesFor(status)...).Set(float64(count)) m.gamesAgreement.WithLabelValues(labelValuesFor(status)...).Set(float64(count))
} }
......
...@@ -12,5 +12,7 @@ func (*NoopMetricsImpl) CacheGet(_ string, _ bool) {} ...@@ -12,5 +12,7 @@ func (*NoopMetricsImpl) CacheGet(_ string, _ bool) {}
func (*NoopMetricsImpl) RecordClaimResolutionDelayMax(delay float64) {} func (*NoopMetricsImpl) RecordClaimResolutionDelayMax(delay float64) {}
func (*NoopMetricsImpl) RecordOutputFetchTime(timestamp float64) {}
func (*NoopMetricsImpl) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {} func (*NoopMetricsImpl) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {}
func (*NoopMetricsImpl) RecordGameAgreement(status GameAgreementStatus, count int) {} func (*NoopMetricsImpl) RecordGameAgreement(status GameAgreementStatus, count int) {}
...@@ -100,7 +100,7 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error ...@@ -100,7 +100,7 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
} }
func (s *Service) initOutputValidator() { func (s *Service) initOutputValidator() {
s.validator = newOutputValidator(s.rollupClient) s.validator = newOutputValidator(s.metrics, s.rollupClient)
} }
func (s *Service) initGameCallerCreator() { func (s *Service) initGameCallerCreator() {
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -14,13 +15,19 @@ type OutputRollupClient interface { ...@@ -14,13 +15,19 @@ type OutputRollupClient interface {
OutputAtBlock(ctx context.Context, blockNum uint64) (*eth.OutputResponse, error) OutputAtBlock(ctx context.Context, blockNum uint64) (*eth.OutputResponse, error)
} }
type OutputMetrics interface {
RecordOutputFetchTime(float64)
}
type outputValidator struct { type outputValidator struct {
client OutputRollupClient metrics OutputMetrics
client OutputRollupClient
} }
func newOutputValidator(client OutputRollupClient) *outputValidator { func newOutputValidator(metrics OutputMetrics, client OutputRollupClient) *outputValidator {
return &outputValidator{ return &outputValidator{
client: client, metrics: metrics,
client: client,
} }
} }
...@@ -35,6 +42,7 @@ func (o *outputValidator) CheckRootAgreement(ctx context.Context, blockNum uint6 ...@@ -35,6 +42,7 @@ func (o *outputValidator) CheckRootAgreement(ctx context.Context, blockNum uint6
} }
return false, common.Hash{}, fmt.Errorf("failed to get output at block: %w", err) return false, common.Hash{}, fmt.Errorf("failed to get output at block: %w", err)
} }
o.metrics.RecordOutputFetchTime(float64(time.Now().Unix()))
expected := common.Hash(output.OutputRoot) expected := common.Hash(output.OutputRoot)
return rootClaim == expected, expected, nil return rootClaim == expected, expected, nil
} }
...@@ -18,45 +18,58 @@ func TestDetector_CheckRootAgreement(t *testing.T) { ...@@ -18,45 +18,58 @@ func TestDetector_CheckRootAgreement(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("OutputFetchFails", func(t *testing.T) { t.Run("OutputFetchFails", func(t *testing.T) {
validator, rollup := setupOutputValidatorTest(t) validator, rollup, metrics := setupOutputValidatorTest(t)
rollup.err = errors.New("boom") rollup.err = errors.New("boom")
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 0, mockRootClaim) agree, fetched, err := validator.CheckRootAgreement(context.Background(), 0, mockRootClaim)
require.ErrorIs(t, err, rollup.err) require.ErrorIs(t, err, rollup.err)
require.Equal(t, common.Hash{}, fetched) require.Equal(t, common.Hash{}, fetched)
require.False(t, agree) require.False(t, agree)
require.Zero(t, metrics.fetchTime)
}) })
t.Run("OutputMismatch", func(t *testing.T) { t.Run("OutputMismatch", func(t *testing.T) {
validator, _ := setupOutputValidatorTest(t) validator, _, metrics := setupOutputValidatorTest(t)
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 0, common.Hash{}) agree, fetched, err := validator.CheckRootAgreement(context.Background(), 0, common.Hash{})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, mockRootClaim, fetched) require.Equal(t, mockRootClaim, fetched)
require.False(t, agree) require.False(t, agree)
require.NotZero(t, metrics.fetchTime)
}) })
t.Run("OutputMatches", func(t *testing.T) { t.Run("OutputMatches", func(t *testing.T) {
validator, _ := setupOutputValidatorTest(t) validator, _, metrics := setupOutputValidatorTest(t)
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 0, mockRootClaim) agree, fetched, err := validator.CheckRootAgreement(context.Background(), 0, mockRootClaim)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, mockRootClaim, fetched) require.Equal(t, mockRootClaim, fetched)
require.True(t, agree) require.True(t, agree)
require.NotZero(t, metrics.fetchTime)
}) })
t.Run("OutputNotFound", func(t *testing.T) { t.Run("OutputNotFound", func(t *testing.T) {
validator, rollup := setupOutputValidatorTest(t) validator, rollup, metrics := setupOutputValidatorTest(t)
// This crazy error is what we actually get back from the API // This crazy error is what we actually get back from the API
rollup.err = errors.New("failed to get L2 block ref with sync status: failed to determine L2BlockRef of height 42984924, could not get payload: not found") rollup.err = errors.New("failed to get L2 block ref with sync status: failed to determine L2BlockRef of height 42984924, could not get payload: not found")
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 42984924, mockRootClaim) agree, fetched, err := validator.CheckRootAgreement(context.Background(), 42984924, mockRootClaim)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, common.Hash{}, fetched) require.Equal(t, common.Hash{}, fetched)
require.False(t, agree) require.False(t, agree)
require.Zero(t, metrics.fetchTime)
}) })
} }
func setupOutputValidatorTest(t *testing.T) (*outputValidator, *stubRollupClient) { func setupOutputValidatorTest(t *testing.T) (*outputValidator, *stubRollupClient, *stubOutputMetrics) {
client := &stubRollupClient{} client := &stubRollupClient{}
validator := newOutputValidator(client) metrics := &stubOutputMetrics{}
return validator, client validator := newOutputValidator(metrics, client)
return validator, client, metrics
}
type stubOutputMetrics struct {
fetchTime float64
}
func (s *stubOutputMetrics) RecordOutputFetchTime(fetchTime float64) {
s.fetchTime = fetchTime
} }
type stubRollupClient struct { type stubRollupClient struct {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment