From 50c7a33f48d9ed76cd14b0dc1ff1d5095b458a7e Mon Sep 17 00:00:00 2001 From: Augustus <14297860+augustbleeds@users.noreply.github.com> Date: Mon, 29 Apr 2024 08:07:03 -0700 Subject: [PATCH] BCI-3127: monitoring report observation length (#418) --- monitoring/cmd/monitoring/main.go | 8 +- .../exporter_transmission_details.go | 78 ++++++++++++++++++ monitoring/pkg/monitoring/metrics.go | 50 +++++++++++ .../monitoring/source_transmission_details.go | 82 +++++++++++++++++++ .../source_transmission_details_test.go | 57 +++++++++++++ 5 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 monitoring/pkg/monitoring/exporter_transmission_details.go create mode 100644 monitoring/pkg/monitoring/source_transmission_details.go create mode 100644 monitoring/pkg/monitoring/source_transmission_details_test.go diff --git a/monitoring/cmd/monitoring/main.go b/monitoring/cmd/monitoring/main.go index eb3c718c4..1a4fbd8ad 100644 --- a/monitoring/cmd/monitoring/main.go +++ b/monitoring/cmd/monitoring/main.go @@ -78,14 +78,18 @@ func main() { return } + // per-feed factories proxySourceFactory := monitoring.NewProxySourceFactory(ocr2Client) - monitor.SourceFactories = append(monitor.SourceFactories, proxySourceFactory) + transmissionsDetailsSourceFactory := monitoring.NewTransmissionDetailsSourceFactory(ocr2Client) + monitor.SourceFactories = append(monitor.SourceFactories, proxySourceFactory, transmissionsDetailsSourceFactory) metricsBuilder := monitoring.NewMetrics(logger.With(log, "component", "starknet-metrics-builder")) prometheusExporterFactory := monitoring.NewPrometheusExporterFactory(metricsBuilder) - monitor.ExporterFactories = append(monitor.ExporterFactories, prometheusExporterFactory) + transmissionsDetailsExporterFactory := monitoring.NewTransmissionDetailsExporterFactory(metricsBuilder) + monitor.ExporterFactories = append(monitor.ExporterFactories, prometheusExporterFactory, transmissionsDetailsExporterFactory) + // network factories nodeBalancesSourceFactory := monitoring.NewNodeBalancesSourceFactory(strTokenClient) monitor.NetworkSourceFactories = append(monitor.NetworkSourceFactories, nodeBalancesSourceFactory) diff --git a/monitoring/pkg/monitoring/exporter_transmission_details.go b/monitoring/pkg/monitoring/exporter_transmission_details.go new file mode 100644 index 000000000..cd7992053 --- /dev/null +++ b/monitoring/pkg/monitoring/exporter_transmission_details.go @@ -0,0 +1,78 @@ +package monitoring + +import ( + "context" + "fmt" + + relayMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" +) + +// NewPrometheusExporterFactory builds an implementation of the Exporter for prometheus. +func NewTransmissionDetailsExporterFactory( + metrics Metrics, +) relayMonitoring.ExporterFactory { + return &transmissionDetailsExporterFactory{ + metrics, + } +} + +type transmissionDetailsExporterFactory struct { + metrics Metrics +} + +func (p *transmissionDetailsExporterFactory) NewExporter( + params relayMonitoring.ExporterParams, +) (relayMonitoring.Exporter, error) { + starknetFeedConfig, ok := params.FeedConfig.(StarknetFeedConfig) + if !ok { + return nil, fmt.Errorf("expected feedConfig to be of type StarknetFeedConfig not %T", params.FeedConfig) + } + return &transmissionDetailsExporter{ + params.ChainConfig, + starknetFeedConfig, + p.metrics, + }, nil +} + +type transmissionDetailsExporter struct { + chainConfig relayMonitoring.ChainConfig + feedConfig StarknetFeedConfig + metrics Metrics +} + +func (p *transmissionDetailsExporter) Export(ctx context.Context, data interface{}) { + transmissionsEnvelope, found := data.(TransmissionsEnvelope) + if !found { + return + } + + for _, t := range transmissionsEnvelope.Transmissions { + observationLength := float64(t.ObservationLength) + p.metrics.SetReportObservations( + observationLength, + p.feedConfig.ContractAddress, + p.feedConfig.GetID(), + p.chainConfig.GetChainID(), + p.feedConfig.GetContractStatus(), + p.feedConfig.GetContractType(), + p.feedConfig.Name, + p.feedConfig.Path, + p.chainConfig.GetNetworkID(), + p.chainConfig.GetNetworkName(), + ) + } +} + +func (p *transmissionDetailsExporter) Cleanup(_ context.Context) { + p.metrics.CleanupReportObservations( + p.feedConfig.GetContractAddress(), + p.feedConfig.GetID(), + p.chainConfig.GetChainID(), + p.feedConfig.GetContractStatus(), + p.feedConfig.GetContractType(), + p.feedConfig.GetName(), + p.feedConfig.GetPath(), + p.chainConfig.GetNetworkID(), + p.chainConfig.GetNetworkName(), + ) +} diff --git a/monitoring/pkg/monitoring/metrics.go b/monitoring/pkg/monitoring/metrics.go index 4b24c3361..bd395ff0a 100644 --- a/monitoring/pkg/monitoring/metrics.go +++ b/monitoring/pkg/monitoring/metrics.go @@ -9,6 +9,8 @@ import ( // Metrics is an interface for prometheus metrics. Makes testing easier. type Metrics interface { + SetReportObservations(answer float64, accountAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) + CleanupReportObservations(accountAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) SetProxyAnswersRaw(answer float64, proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) SetProxyAnswers(answer float64, proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) CleanupProxy(proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) @@ -17,6 +19,23 @@ type Metrics interface { } var ( + reportObservations = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "report_observations", + Help: "Reports # of observations included in a transmission report", + }, + []string{ + "account_address", + "feed_id", + "chain_id", + "contract_status", + "contract_type", + "feed_name", + "feed_path", + "network_id", + "network_name", + }, + ) proxyAnswersRaw = promauto.NewGaugeVec( prometheus.GaugeOpts{ Name: "proxy_answers_raw", @@ -72,6 +91,37 @@ func (d *defaultMetrics) CleanupBalance(contractAddress, alias, networkId, netwo } } +func (d *defaultMetrics) SetReportObservations(answer float64, accountAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) { + reportObservations.With(prometheus.Labels{ + "account_address": accountAddress, + "feed_id": feedID, + "chain_id": chainID, + "contract_status": contractStatus, + "contract_type": contractType, + "feed_name": feedName, + "feed_path": feedPath, + "network_id": networkID, + "network_name": networkName, + }).Set(answer) +} + +func (d *defaultMetrics) CleanupReportObservations(accountAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) { + labels := prometheus.Labels{ + "account_address": accountAddress, + "feed_id": feedID, + "chain_id": chainID, + "contract_status": contractStatus, + "contract_type": contractType, + "feed_name": feedName, + "feed_path": feedPath, + "network_id": networkID, + "network_name": networkName, + } + if !reportObservations.Delete(labels) { + d.log.Errorw("failed to delete metric", "name", "report_observations", "labels", labels) + } +} + func (d *defaultMetrics) SetProxyAnswersRaw(answer float64, proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) { proxyAnswersRaw.With(prometheus.Labels{ "proxy_contract_address": proxyContractAddress, diff --git a/monitoring/pkg/monitoring/source_transmission_details.go b/monitoring/pkg/monitoring/source_transmission_details.go new file mode 100644 index 000000000..d0dd3c177 --- /dev/null +++ b/monitoring/pkg/monitoring/source_transmission_details.go @@ -0,0 +1,82 @@ +package monitoring + +import ( + "context" + "fmt" + "math/big" + + "github.com/NethermindEth/juno/core/felt" + starknetutils "github.com/NethermindEth/starknet.go/utils" + + relayMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + + "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/ocr2" +) + +type TransmissionInfo struct { + GasPrice *big.Int + ObservationLength uint32 +} + +type TransmissionsEnvelope struct { + Transmissions []TransmissionInfo +} + +func NewTransmissionDetailsSourceFactory( + ocr2Reader ocr2.OCR2Reader, +) relayMonitoring.SourceFactory { + return &transmissionDetailsSourceFactory{ + ocr2Reader, + } +} + +type transmissionDetailsSourceFactory struct { + ocr2Reader ocr2.OCR2Reader +} + +func (s *transmissionDetailsSourceFactory) NewSource( + _ relayMonitoring.ChainConfig, + feedConfig relayMonitoring.FeedConfig, +) (relayMonitoring.Source, error) { + starknetFeedConfig, ok := feedConfig.(StarknetFeedConfig) + if !ok { + return nil, fmt.Errorf("expected feedConfig to be of type StarknetFeedConfig not %T", feedConfig) + } + contractAddress, err := starknetutils.HexToFelt(starknetFeedConfig.ContractAddress) + if err != nil { + return nil, err + } + return &transmissionDetailsSource{ + contractAddress, + s.ocr2Reader, + }, nil +} + +func (s *transmissionDetailsSourceFactory) GetType() string { + return "transmission details" +} + +type transmissionDetailsSource struct { + contractAddress *felt.Felt + ocr2Reader ocr2.OCR2Reader +} + +func (s *transmissionDetailsSource) Fetch(ctx context.Context) (interface{}, error) { + latestRound, err := s.ocr2Reader.LatestRoundData(ctx, s.contractAddress) + if err != nil { + return nil, fmt.Errorf("failed to fetch latest_round_data: %w", err) + } + transmissions, err := s.ocr2Reader.NewTransmissionsFromEventsAt(ctx, s.contractAddress, latestRound.BlockNumber) + if err != nil { + return nil, fmt.Errorf("couldn't fetch transmission events: %w", err) + } + var envelope TransmissionsEnvelope + for _, t := range transmissions { + envelope.Transmissions = append( + envelope.Transmissions, + TransmissionInfo{GasPrice: t.GasPrice, ObservationLength: t.ObservationsLen}, + ) + } + + return envelope, nil +} diff --git a/monitoring/pkg/monitoring/source_transmission_details_test.go b/monitoring/pkg/monitoring/source_transmission_details_test.go new file mode 100644 index 000000000..3b7d07528 --- /dev/null +++ b/monitoring/pkg/monitoring/source_transmission_details_test.go @@ -0,0 +1,57 @@ +package monitoring + +import ( + "context" + "math/big" + "testing" + + starknetutils "github.com/NethermindEth/starknet.go/utils" + "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/ocr2" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + ocr2Mocks "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/ocr2/mocks" +) + +func TestTransmissionDetailsSource(t *testing.T) { + chainConfig := generateChainConfig() + feedConfig := generateFeedConfig() + + contractAddressFelt, err := starknetutils.HexToFelt(feedConfig.ContractAddress) + require.NoError(t, err) + + ocr2Reader := ocr2Mocks.NewOCR2Reader(t) + blockNumber := uint64(777) + ocr2Reader.On( + "LatestRoundData", + mock.Anything, // ctx + contractAddressFelt, + ).Return(ocr2.RoundData{BlockNumber: blockNumber}, nil).Once() + ocr2Reader.On( + "NewTransmissionsFromEventsAt", + mock.Anything, // ctx + contractAddressFelt, + blockNumber, + ).Return( + []ocr2.NewTransmissionEvent{ + { + GasPrice: new(big.Int).SetUint64(7), + ObservationsLen: 7, + }, + }, + nil, + ).Once() + + factory := NewTransmissionDetailsSourceFactory(ocr2Reader) + source, err := factory.NewSource(chainConfig, feedConfig) + require.NoError(t, err) + + transmissionsEnvelope, err := source.Fetch(context.Background()) + require.NoError(t, err) + envelope, ok := transmissionsEnvelope.(TransmissionsEnvelope) + require.True(t, ok) + + require.Equal(t, len(envelope.Transmissions), 1) + require.Equal(t, envelope.Transmissions[0].GasPrice.Uint64(), uint64(7)) + require.Equal(t, envelope.Transmissions[0].ObservationLength, uint32(7)) +}