diff --git a/monitoring/pkg/monitoring/exporter_transmission_details.go b/monitoring/pkg/monitoring/exporter_transmission_details.go index cd7992053..64cc5e99e 100644 --- a/monitoring/pkg/monitoring/exporter_transmission_details.go +++ b/monitoring/pkg/monitoring/exporter_transmission_details.go @@ -3,6 +3,7 @@ package monitoring import ( "context" "fmt" + "math/big" relayMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" ) @@ -47,6 +48,23 @@ func (p *transmissionDetailsExporter) Export(ctx context.Context, data interface } for _, t := range transmissionsEnvelope.Transmissions { + // gas price + divisor := new(big.Int).Exp(new(big.Int).SetUint64(10), new(big.Int).SetUint64(18), nil) // 10^18 + gasPriceInSTRK := new(big.Int).Div(t.GasPrice, divisor) + p.metrics.SetTransmissionGasPrice( + toFloat64(gasPriceInSTRK), + 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(), + ) + + // observation length observationLength := float64(t.ObservationLength) p.metrics.SetReportObservations( observationLength, @@ -64,6 +82,17 @@ func (p *transmissionDetailsExporter) Export(ctx context.Context, data interface } func (p *transmissionDetailsExporter) Cleanup(_ context.Context) { + p.metrics.CleanupTransmissionGasPrice( + 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(), + ) p.metrics.CleanupReportObservations( p.feedConfig.GetContractAddress(), p.feedConfig.GetID(), diff --git a/monitoring/pkg/monitoring/exporter_transmission_details_test.go b/monitoring/pkg/monitoring/exporter_transmission_details_test.go new file mode 100644 index 000000000..b8597cd3d --- /dev/null +++ b/monitoring/pkg/monitoring/exporter_transmission_details_test.go @@ -0,0 +1,97 @@ +package monitoring + +import ( + "context" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-starknet/monitoring/pkg/monitoring/mocks" + + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" +) + +func TestTransmissionDetailsExporter(t *testing.T) { + chainConfig := generateChainConfig() + feedConfig := generateFeedConfig() + + mockMetrics := mocks.NewMetrics(t) + factory := NewTransmissionDetailsExporterFactory(mockMetrics) + + gasPrice, ok := new(big.Int).SetString("10000000000000000000", 10) + require.True(t, ok) + + envelope := TransmissionsEnvelope{ + Transmissions: []TransmissionInfo{{ + GasPrice: gasPrice, // 10 STRK (10^19 FRI) + ObservationLength: 123, + }, + }, + } + + mockMetrics.On( + "SetTransmissionGasPrice", + float64(10), + feedConfig.ContractAddress, + feedConfig.GetID(), + chainConfig.GetChainID(), + feedConfig.GetContractStatus(), + feedConfig.GetContractType(), + feedConfig.Name, + feedConfig.Path, + chainConfig.GetNetworkID(), + chainConfig.GetNetworkName(), + ).Once() + + mockMetrics.On( + "SetReportObservations", + float64(123), + feedConfig.ContractAddress, + feedConfig.GetID(), + chainConfig.GetChainID(), + feedConfig.GetContractStatus(), + feedConfig.GetContractType(), + feedConfig.Name, + feedConfig.Path, + chainConfig.GetNetworkID(), + chainConfig.GetNetworkName(), + ).Once() + + exporter, err := factory.NewExporter(commonMonitoring.ExporterParams{ + ChainConfig: chainConfig, + FeedConfig: feedConfig, + }) + require.NoError(t, err) + + exporter.Export(context.Background(), envelope) + + // cleanup + mockMetrics.On( + "CleanupReportObservations", + feedConfig.ContractAddress, + feedConfig.GetID(), + chainConfig.GetChainID(), + feedConfig.GetContractStatus(), + feedConfig.GetContractType(), + feedConfig.Name, + feedConfig.Path, + chainConfig.GetNetworkID(), + chainConfig.GetNetworkName(), + ).Once() + mockMetrics.On( + "CleanupTransmissionGasPrice", + feedConfig.ContractAddress, + feedConfig.GetID(), + chainConfig.GetChainID(), + feedConfig.GetContractStatus(), + feedConfig.GetContractType(), + feedConfig.Name, + feedConfig.Path, + chainConfig.GetNetworkID(), + chainConfig.GetNetworkName(), + ).Once() + + exporter.Cleanup(context.Background()) + +} diff --git a/monitoring/pkg/monitoring/metrics.go b/monitoring/pkg/monitoring/metrics.go index bd395ff0a..00bf03fca 100644 --- a/monitoring/pkg/monitoring/metrics.go +++ b/monitoring/pkg/monitoring/metrics.go @@ -8,7 +8,11 @@ import ( ) // Metrics is an interface for prometheus metrics. Makes testing easier. +// +//go:generate mockery --name Metrics --output ./mocks/ type Metrics interface { + SetTransmissionGasPrice(answer float64, contractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) + CleanupTransmissionGasPrice(contractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) 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) @@ -19,12 +23,30 @@ type Metrics interface { } var ( + transmissionGasPrice = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "starknet_transmission_gas_price", + Help: "Reports gas price (units STRK) reported with transmission", + }, + []string{ + "contract_address", + "feed_id", + "chain_id", + "contract_status", + "contract_type", + "feed_name", + "feed_path", + "network_id", + "network_name", + }, + ) reportObservations = promauto.NewGaugeVec( prometheus.GaugeOpts{ Name: "report_observations", Help: "Reports # of observations included in a transmission report", }, []string{ + // uses "account_address" instead of "contract_address" for consistency with solana dashboards "account_address", "feed_id", "chain_id", @@ -68,6 +90,37 @@ type defaultMetrics struct { log relayMonitoring.Logger } +func (d *defaultMetrics) SetTransmissionGasPrice(answer float64, contractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) { + transmissionGasPrice.With(prometheus.Labels{ + "contract_address": contractAddress, + "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) CleanupTransmissionGasPrice(contractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName string) { + labels := prometheus.Labels{ + "contract_address": contractAddress, + "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 !transmissionGasPrice.Delete(labels) { + d.log.Errorw("failed to delete metric", "name", "starknet_transmission_gas_price", "labels", labels) + } +} + func (d *defaultMetrics) SetBalance(answer float64, contractAddress, alias, networkId, networkName, chainID string) { contractBalance.With(prometheus.Labels{ "contract_address": contractAddress, diff --git a/monitoring/pkg/monitoring/mocks/Metrics.go b/monitoring/pkg/monitoring/mocks/Metrics.go new file mode 100644 index 000000000..9606ea96e --- /dev/null +++ b/monitoring/pkg/monitoring/mocks/Metrics.go @@ -0,0 +1,70 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// Metrics is an autogenerated mock type for the Metrics type +type Metrics struct { + mock.Mock +} + +// CleanupBalance provides a mock function with given fields: contractAddress, alias, networkId, networkName, chainID +func (_m *Metrics) CleanupBalance(contractAddress string, alias string, networkId string, networkName string, chainID string) { + _m.Called(contractAddress, alias, networkId, networkName, chainID) +} + +// CleanupProxy provides a mock function with given fields: proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName +func (_m *Metrics) CleanupProxy(proxyContractAddress string, feedID string, chainID string, contractStatus string, contractType string, feedName string, feedPath string, networkID string, networkName string) { + _m.Called(proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName) +} + +// CleanupReportObservations provides a mock function with given fields: accountAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName +func (_m *Metrics) CleanupReportObservations(accountAddress string, feedID string, chainID string, contractStatus string, contractType string, feedName string, feedPath string, networkID string, networkName string) { + _m.Called(accountAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName) +} + +// CleanupTransmissionGasPrice provides a mock function with given fields: contractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName +func (_m *Metrics) CleanupTransmissionGasPrice(contractAddress string, feedID string, chainID string, contractStatus string, contractType string, feedName string, feedPath string, networkID string, networkName string) { + _m.Called(contractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName) +} + +// SetBalance provides a mock function with given fields: answer, contractAddress, alias, networkId, networkName, chainID +func (_m *Metrics) SetBalance(answer float64, contractAddress string, alias string, networkId string, networkName string, chainID string) { + _m.Called(answer, contractAddress, alias, networkId, networkName, chainID) +} + +// SetProxyAnswers provides a mock function with given fields: answer, proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName +func (_m *Metrics) SetProxyAnswers(answer float64, proxyContractAddress string, feedID string, chainID string, contractStatus string, contractType string, feedName string, feedPath string, networkID string, networkName string) { + _m.Called(answer, proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName) +} + +// SetProxyAnswersRaw provides a mock function with given fields: answer, proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName +func (_m *Metrics) SetProxyAnswersRaw(answer float64, proxyContractAddress string, feedID string, chainID string, contractStatus string, contractType string, feedName string, feedPath string, networkID string, networkName string) { + _m.Called(answer, proxyContractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName) +} + +// SetReportObservations provides a mock function with given fields: answer, accountAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName +func (_m *Metrics) SetReportObservations(answer float64, accountAddress string, feedID string, chainID string, contractStatus string, contractType string, feedName string, feedPath string, networkID string, networkName string) { + _m.Called(answer, accountAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName) +} + +// SetTransmissionGasPrice provides a mock function with given fields: answer, contractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName +func (_m *Metrics) SetTransmissionGasPrice(answer float64, contractAddress string, feedID string, chainID string, contractStatus string, contractType string, feedName string, feedPath string, networkID string, networkName string) { + _m.Called(answer, contractAddress, feedID, chainID, contractStatus, contractType, feedName, feedPath, networkID, networkName) +} + +type mockConstructorTestingTNewMetrics interface { + mock.TestingT + Cleanup(func()) +} + +// NewMetrics creates a new instance of Metrics. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMetrics(t mockConstructorTestingTNewMetrics) *Metrics { + mock := &Metrics{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/monitoring/pkg/monitoring/source_contract_balance_test.go b/monitoring/pkg/monitoring/source_contract_balance_test.go index fafc3d801..7ccae8284 100644 --- a/monitoring/pkg/monitoring/source_contract_balance_test.go +++ b/monitoring/pkg/monitoring/source_contract_balance_test.go @@ -13,19 +13,9 @@ import ( ) func TestContractBalancesSource(t *testing.T) { - // This test makes sure that the mapping between the response from the ocr2.Client - // method calls and the output of the Proxy source is correct. - chainConfig := generateChainConfig() nodeConfig := generateNodeConfig() - // ocr2Reader := ocr2Mocks.NewOCR2Reader(t) - // ocr2Reader.On( - // "LatestRoundData", - // mock.Anything, // ctx - // proxyContractAddressFelt, - // ).Return(ocr2ClientLatestRoundDataResponseForProxy, nil).Once() - erc20Reader := erc20Mocks.NewERC20Reader(t) for _, x := range nodeConfig {