package exporters import ( "context" "math/big" "github.com/cornelk/hashmap" "github.com/ethereum/go-ethereum/common" "github.com/hedzr/log" "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) type submitterMetadata struct { address common.Address name string nonce int64 balance float64 } type relayerMetadata struct { address common.Address balance float64 usdcBalance float64 } //go:generate go run github.com/vburenin/ifacemaker -f otel.go -s otelRecorder -i iOtelRecorder -p exporters -o otel_generated.go -c "autogenerated file" type otelRecorder struct { metrics metrics.Handler meter metric.Meter // VPRICE vPrice *hashmap.Map[int, float64] vpriceGauge metric.Float64ObservableGauge // BRIDGE // chainID -> []tokenData td *hashmap.Map[int, []tokenData] // How much gas is left on the bridge. gasBalance *hashmap.Map[int, float64] // tokenData field gauges gasBalanceGauge metric.Float64ObservableGauge bridgeBalanceGauge metric.Float64ObservableGauge feeBalanceGauge metric.Float64ObservableGauge totalSupplyGauge metric.Float64ObservableGauge // submitter stats submitters *hashmap.Map[int, []submitterMetadata] balanceGauge metric.Float64ObservableGauge nonceGauge metric.Int64ObservableGauge // relayer stats relayerBalance *hashmap.Map[int, []relayerMetadata] relayerBalanceGauge metric.Float64ObservableGauge relayerUSDCBalanceGuage metric.Float64ObservableGauge } // TODO: unexport all methods. // nolint: cyclop func newOtelRecorder(meterHandler metrics.Handler) iOtelRecorder { otr := otelRecorder{ metrics: meterHandler, meter: meterHandler.Meter(meterName), vPrice: hashmap.New[int, float64](), gasBalance: hashmap.New[int, float64](), td: hashmap.New[int, []tokenData](), submitters: hashmap.New[int, []submitterMetadata](), relayerBalance: hashmap.New[int, []relayerMetadata](), } var err error if otr.vpriceGauge, err = otr.meter.Float64ObservableGauge("vpriceMetric"); err != nil { log.Warnf("failed to create vprice gauge: %v", err) } if otr.bridgeBalanceGauge, err = otr.meter.Float64ObservableGauge("bridgeBalanceMetric"); err != nil { log.Warnf("failed to create bridgeBalance gauge: %v", err) } if otr.feeBalanceGauge, err = otr.meter.Float64ObservableGauge("feeBalance_total"); err != nil { log.Warnf("failed to create feeBalance gauge: %v", err) } if otr.totalSupplyGauge, err = otr.meter.Float64ObservableGauge("totalSupply"); err != nil { log.Warnf("failed to create totalSupply gauge: %v", err) } if otr.gasBalanceGauge, err = otr.meter.Float64ObservableGauge("gasBalance"); err != nil { log.Warnf("failed to create gasBalance gauge: %v", err) } if otr.balanceGauge, err = otr.meter.Float64ObservableGauge("gas_balance"); err != nil { log.Warnf("failed to create balance gauge: %v", err) } if otr.nonceGauge, err = otr.meter.Int64ObservableGauge("nonce"); err != nil { log.Warnf("failed to create nonce gauge: %v", err) } if otr.relayerBalanceGauge, err = otr.meter.Float64ObservableGauge("relayer_balance"); err != nil { log.Warnf("failed to create relayerBalance gauge: %v", err) } if otr.relayerUSDCBalanceGuage, err = otr.meter.Float64ObservableGauge("relayer_usdc_balance"); err != nil { log.Warnf("failed to create relayerUSDCBalance gauge: %v", err) } // Register VPrice callback if _, err = otr.meter.RegisterCallback(otr.recordVpriceGauge, otr.vpriceGauge); err != nil { log.Warnf("failed to register callback for vprice metrics: %v", err) } // Register Token Balance Callback if _, err = otr.meter.RegisterCallback( otr.recordTokenBalance, otr.bridgeBalanceGauge, otr.feeBalanceGauge, otr.totalSupplyGauge, ); err != nil { log.Warnf("failed to register callback for bridge metrics : %v", err) } // Register Submitter Stats Callback if _, err = otr.meter.RegisterCallback(otr.recordSubmitterStats, otr.balanceGauge, otr.nonceGauge); err != nil { log.Warnf("failed to register callback for submitter metrics: %v", err) } if _, err = otr.meter.RegisterCallback(otr.recordBridgeGasBalance, otr.gasBalanceGauge); err != nil { log.Warnf("failed to register callback for bridge gas balance metrics: %v", err) } // The Relayer code of interest. if _, err = otr.meter.RegisterCallback(otr.recordRelayerBalance, otr.relayerBalanceGauge, otr.relayerUSDCBalanceGuage); err != nil { log.Warnf("failed to register callback for relayer balance metrics: %v", err) } return &otr } // Virtual Price Metrics. func (o *otelRecorder) RecordVPrice(chainid int, vPrice float64) { o.vPrice.Set(chainid, vPrice) } func (o *otelRecorder) recordVpriceGauge( _ context.Context, observer metric.Observer, ) (err error) { if o.metrics == nil || o.vpriceGauge == nil { return nil } o.vPrice.Range( func(chainid int, vprice float64) bool { observer.ObserveFloat64( o.vpriceGauge, vprice, metric.WithAttributes(attribute.Int(metrics.ChainID, chainid)), ) return true }, ) return nil } // Token Balance Metrics. func (o *otelRecorder) RecordBridgeGasBalance(chainid int, gasBalance float64) { o.gasBalance.Set(chainid, gasBalance) } func (o *otelRecorder) recordBridgeGasBalance(_ context.Context, observer metric.Observer) (err error) { if o.metrics == nil || o.bridgeBalanceGauge == nil { return nil } o.gasBalance.Range(func(chainID int, gasBalance float64) bool { observer.ObserveFloat64( o.gasBalanceGauge, gasBalance, metric.WithAttributes(attribute.Int(metrics.ChainID, chainID)), ) return true }) return nil } type tokenData struct { metadata TokenConfig contractBalance *big.Int totalSuppply *big.Int feeBalance *big.Int } func (o *otelRecorder) RecordTokenBalance( chainID int, tokenData tokenData, ) { td, _ := o.td.Get(chainID) td = append(td, tokenData) o.td.Set(chainID, td) } func (o *otelRecorder) recordTokenBalance( _ context.Context, observer metric.Observer, ) (err error) { if o.metrics == nil || o.bridgeBalanceGauge == nil { return nil } o.td.Range(func(_ int, td []tokenData) bool { for _, token := range td { tokenAttributes := attribute.NewSet( attribute.String("tokenID", token.metadata.TokenID), attribute.Int(metrics.ChainID, token.metadata.ChainID), ) bridgeBalance := core.BigToDecimals(token.contractBalance, token.metadata.TokenDecimals) observer.ObserveFloat64( o.bridgeBalanceGauge, bridgeBalance, metric.WithAttributeSet(tokenAttributes), ) feeBalance := core.BigToDecimals(token.feeBalance, token.metadata.TokenDecimals) observer.ObserveFloat64( o.feeBalanceGauge, feeBalance, metric.WithAttributeSet(tokenAttributes), ) totalSupply := core.BigToDecimals(token.totalSuppply, token.metadata.TokenDecimals) observer.ObserveFloat64( o.totalSupplyGauge, totalSupply, metric.WithAttributeSet(tokenAttributes), ) } return true }) return nil } // Submitter stats. func (o *otelRecorder) RecordSubmitterStats(chainid int, metadata submitterMetadata) { submitters, _ := o.submitters.Get(chainid) submitters = append(submitters, metadata) o.submitters.Set(chainid, submitters) } func (o *otelRecorder) recordSubmitterStats( _ context.Context, observer metric.Observer, ) (err error) { if o.metrics == nil || o.nonceGauge == nil { return nil } o.submitters.Range(func(chainID int, submitters []submitterMetadata) bool { for _, submitter := range submitters { observer.ObserveInt64( o.nonceGauge, submitter.nonce, metric.WithAttributes(attribute.Int(metrics.ChainID, chainID), attribute.String(metrics.EOAAddress, submitter.address.String()), attribute.String("name", submitter.name), ), ) observer.ObserveFloat64( o.balanceGauge, submitter.balance, metric.WithAttributes( attribute.Int(metrics.ChainID, chainID), attribute.String(metrics.EOAAddress, submitter.address.String()), attribute.String("name", submitter.name), ), ) } return true }) return nil } // RELAYER CODE. func (o *otelRecorder) RecordRelayerBalance(chainID int, relayer relayerMetadata) { relayerBalances, _ := o.relayerBalance.Get(chainID) relayerBalances = append(relayerBalances, relayer) o.relayerBalance.Set(chainID, relayerBalances) } func (o *otelRecorder) recordRelayerBalance( _ context.Context, observer metric.Observer, ) (err error) { if o.metrics == nil || o.relayerBalance == nil { return nil } o.relayerBalance.Range(func(chainID int, relayerBalances []relayerMetadata) bool { for _, relayer := range relayerBalances { observer.ObserveFloat64( o.relayerBalanceGauge, relayer.balance, metric.WithAttributes( attribute.Int(metrics.ChainID, chainID), attribute.String("relayer_address", relayer.address.String()), ), ) observer.ObserveFloat64( o.relayerUSDCBalanceGuage, relayer.usdcBalance, metric.WithAttributes( attribute.Int(metrics.ChainID, chainID), attribute.String("relayer_address", relayer.address.String()), ), ) } return true }) return nil }