Skip to content

Commit

Permalink
implement exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
aalu1418 committed Apr 17, 2024
1 parent 99ea507 commit fada7b5
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 75 deletions.
10 changes: 10 additions & 0 deletions cmd/monitoring/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,13 @@ func main() {
chainReader,
logger.With(log, "component", "source-node-balances"),
)
slotHeightSourceFactory := monitoring.NewSlotHeightSourceFactory(
chainReader,
logger.With(log, "component", "souce-slot-height"),
)
monitor.NetworkSourceFactories = append(monitor.NetworkSourceFactories,
nodeBalancesSourceFactory,
slotHeightSourceFactory,
)

// per-feed exporters
Expand All @@ -99,8 +104,13 @@ func main() {
logger.With(log, "component", "solana-prom-exporter"),
metrics.NewNodeBalances,
)
slotHeightExporterFactory := exporter.NewSlotHeightFactory(
logger.With(log, "component", "solana-prom-exporter"),
metrics.NewSlotHeight(logger.With(log, "component", "solana-metrics")),
)
monitor.NetworkExporterFactories = append(monitor.NetworkExporterFactories,
nodeBalancesExporterFactory,
slotHeightExporterFactory,
)

monitor.Run()
Expand Down
70 changes: 19 additions & 51 deletions pkg/monitoring/exporter/reportobservations.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,75 +8,43 @@ import (
"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types"
)

func NewReportObservationsFactory(
log commonMonitoring.Logger,
metrics metrics.ReportObservations,
func NewSlotHeightFactory(
_ commonMonitoring.Logger,
metrics metrics.SlotHeight,
) commonMonitoring.ExporterFactory {
return &reportObservationsFactory{
log,
return &slotHeightFactory{
metrics,
}
}

type reportObservationsFactory struct {
log commonMonitoring.Logger
metrics metrics.ReportObservations
type slotHeightFactory struct {
metrics metrics.SlotHeight
}

func (p *reportObservationsFactory) NewExporter(
func (p *slotHeightFactory) NewExporter(
params commonMonitoring.ExporterParams,
) (commonMonitoring.Exporter, error) {
return &reportObservations{
metrics.FeedInput{
AccountAddress: params.FeedConfig.GetContractAddress(),
FeedID: params.FeedConfig.GetContractAddress(),
ChainID: params.ChainConfig.GetChainID(),
ContractStatus: params.FeedConfig.GetContractStatus(),
ContractType: params.FeedConfig.GetContractType(),
FeedName: params.FeedConfig.GetName(),
FeedPath: params.FeedConfig.GetPath(),
NetworkID: params.ChainConfig.GetNetworkID(),
NetworkName: params.ChainConfig.GetNetworkName(),
},
p.log,
return &slotHeight{
params.ChainConfig.GetNetworkName(),
params.ChainConfig.GetRPCEndpoint(),
p.metrics,
}, nil
}

type reportObservations struct {
label metrics.FeedInput // static for each feed
log commonMonitoring.Logger
metrics metrics.ReportObservations
type slotHeight struct {
chain, url string
metrics metrics.SlotHeight
}

func (p *reportObservations) Export(ctx context.Context, data interface{}) {
details, err := types.MakeTxDetails(data)
if err != nil {
func (p *slotHeight) Export(ctx context.Context, data interface{}) {
slot, ok := data.(types.SlotHeight)
if !ok {
return // skip if input could not be parsed
}

// skip on no updates
if len(details) == 0 {
return
}

// sanity check: find non-empty detail
// assumption: details ordered from latest -> earliest
var latest types.TxDetails
for _, d := range details {
if !d.Empty() {
latest = d
break
}
}
if latest.Empty() {
p.log.Errorw("exporter could not find non-empty TxDetails", "feed", p.label.ToPromLabels())
return
}

p.metrics.SetCount(latest.ObservationCount, p.label)
p.metrics.Set(slot, p.chain, p.url)
}

func (p *reportObservations) Cleanup(_ context.Context) {
p.metrics.Cleanup(p.label)
func (p *slotHeight) Cleanup(_ context.Context) {
p.metrics.Cleanup()
}
34 changes: 10 additions & 24 deletions pkg/monitoring/exporter/reportobservations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ package exporter
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring"
Expand All @@ -16,35 +14,23 @@ import (
"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types"
)

func TestReportObservations(t *testing.T) {
func TestSlotHeight(t *testing.T) {
ctx := utils.Context(t)
lgr, logs := logger.TestObserved(t, zapcore.ErrorLevel)
m := mocks.NewReportObservations(t)
m.On("SetCount", mock.Anything, mock.Anything).Once()
m.On("Cleanup", mock.Anything).Once()
m := mocks.NewSlotHeight(t)
m.On("Set", mock.Anything, mock.Anything, mock.Anything).Once()
m.On("Cleanup").Once()

factory := NewReportObservationsFactory(lgr, m)
factory := NewSlotHeightFactory(logger.Test(t), m)

chainConfig := testutils.GenerateChainConfig()
feedConfig := testutils.GenerateFeedConfig()
exporter, err := factory.NewExporter(commonMonitoring.ExporterParams{ChainConfig: chainConfig, FeedConfig: feedConfig, Nodes: []commonMonitoring.NodeConfig{}})
exporter, err := factory.NewExporter(commonMonitoring.ExporterParams{ChainConfig: chainConfig})
require.NoError(t, err)

// happy path
exporter.Export(ctx, []types.TxDetails{{ObservationCount: 10}})
exporter.Export(ctx, types.SlotHeight(10))
exporter.Cleanup(ctx)

// not txdetails type - no calls to mock
assert.NotPanics(t, func() { exporter.Export(ctx, 1) })

// zero txdetails - no calls to mock
exporter.Export(ctx, []types.TxDetails{})

// empty txdetails
exporter.Export(ctx, []types.TxDetails{{}})
assert.Equal(t, 1, logs.FilterMessage("exporter could not find non-empty TxDetails").Len())

// multiple TxDetails should only call for the first non-empty one
m.On("SetCount", uint8(1), mock.Anything).Once()
exporter.Export(ctx, []types.TxDetails{{}, {ObservationCount: 1}, {ObservationCount: 10}})
// test passing uint64 instead of SlotHeight - should not call mock
// SlotHeight alias of uint64
exporter.Export(ctx, uint64(10))
}
82 changes: 82 additions & 0 deletions pkg/monitoring/exporter/slotheight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package exporter

import (
"context"

commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring"
"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/metrics"
"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types"
)

func NewReportObservationsFactory(
log commonMonitoring.Logger,
metrics metrics.ReportObservations,
) commonMonitoring.ExporterFactory {
return &reportObservationsFactory{
log,
metrics,
}
}

type reportObservationsFactory struct {
log commonMonitoring.Logger
metrics metrics.ReportObservations
}

func (p *reportObservationsFactory) NewExporter(
params commonMonitoring.ExporterParams,
) (commonMonitoring.Exporter, error) {
return &reportObservations{
metrics.FeedInput{
AccountAddress: params.FeedConfig.GetContractAddress(),
FeedID: params.FeedConfig.GetContractAddress(),
ChainID: params.ChainConfig.GetChainID(),
ContractStatus: params.FeedConfig.GetContractStatus(),
ContractType: params.FeedConfig.GetContractType(),
FeedName: params.FeedConfig.GetName(),
FeedPath: params.FeedConfig.GetPath(),
NetworkID: params.ChainConfig.GetNetworkID(),
NetworkName: params.ChainConfig.GetNetworkName(),
},
p.log,
p.metrics,
}, nil
}

type reportObservations struct {
label metrics.FeedInput // static for each feed
log commonMonitoring.Logger
metrics metrics.ReportObservations
}

func (p *reportObservations) Export(ctx context.Context, data interface{}) {
details, err := types.MakeTxDetails(data)
if err != nil {
return // skip if input could not be parsed
}

// skip on no updates
if len(details) == 0 {
return
}

// sanity check: find non-empty detail
// assumption: details ordered from latest -> earliest
var latest types.TxDetails
for _, d := range details {
if !d.Empty() {
latest = d
break
}
}
if latest.Empty() {
p.log.Errorw("exporter could not find non-empty TxDetails", "feed", p.label.ToPromLabels())
return
}

p.metrics.SetCount(latest.ObservationCount, p.label)
}

func (p *reportObservations) Cleanup(_ context.Context) {
p.metrics.Cleanup(p.label)
}
50 changes: 50 additions & 0 deletions pkg/monitoring/exporter/slotheight_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package exporter

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring"
"github.com/smartcontractkit/chainlink-common/pkg/utils"
"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/metrics/mocks"
"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/testutils"
"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types"
)

func TestReportObservations(t *testing.T) {
ctx := utils.Context(t)
lgr, logs := logger.TestObserved(t, zapcore.ErrorLevel)
m := mocks.NewReportObservations(t)
m.On("SetCount", mock.Anything, mock.Anything).Once()
m.On("Cleanup", mock.Anything).Once()

factory := NewReportObservationsFactory(lgr, m)

chainConfig := testutils.GenerateChainConfig()
feedConfig := testutils.GenerateFeedConfig()
exporter, err := factory.NewExporter(commonMonitoring.ExporterParams{ChainConfig: chainConfig, FeedConfig: feedConfig, Nodes: []commonMonitoring.NodeConfig{}})
require.NoError(t, err)

// happy path
exporter.Export(ctx, []types.TxDetails{{ObservationCount: 10}})
exporter.Cleanup(ctx)

// not txdetails type - no calls to mock
assert.NotPanics(t, func() { exporter.Export(ctx, 1) })

// zero txdetails - no calls to mock
exporter.Export(ctx, []types.TxDetails{})

// empty txdetails
exporter.Export(ctx, []types.TxDetails{{}})
assert.Equal(t, 1, logs.FilterMessage("exporter could not find non-empty TxDetails").Len())

// multiple TxDetails should only call for the first non-empty one
m.On("SetCount", uint8(1), mock.Anything).Once()
exporter.Export(ctx, []types.TxDetails{{}, {ObservationCount: 1}, {ObservationCount: 10}})
}

0 comments on commit fada7b5

Please sign in to comment.