synapsecns/sanguine

View on GitHub
contrib/promexporter/exporters/otel.go

Summary

Maintainability
A
0 mins
Test Coverage
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

    // dfk stats
    stuckHeroes      *hashmap.Map[string, int64]
    stuckHeroesGauge metric.Int64ObservableGauge

    // 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),
        stuckHeroes:    hashmap.New[string, int64](),
        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.stuckHeroesGauge, err = otr.meter.Int64ObservableGauge("dfk_pending_heroes"); err != nil {
        log.Warnf("failed to create stuckHeroes 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 DFK Stuck Heroes Callback
    if _, err = otr.meter.RegisterCallback(otr.recordStuckHeroCount, otr.stuckHeroesGauge); err != nil {
        log.Warnf("failed to register callback for dfk stuck heroes 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
}

// DFK Metrics.
func (o *otelRecorder) RecordStuckHeroCount(stuckHeroes int64, chainname string) {
    o.stuckHeroes.Set(chainname, stuckHeroes)
}

func (o *otelRecorder) recordStuckHeroCount(
    _ context.Context,
    observer metric.Observer,
) (err error) {
    if o.metrics == nil || o.stuckHeroesGauge == nil {
        return nil
    }

    o.stuckHeroes.Range(
        func(chainName string, stuckHeroes int64) bool {
            observer.ObserveInt64(
                o.stuckHeroesGauge,
                stuckHeroes,
                metric.WithAttributes(
                    attribute.String("chain_name", chainName),
                ),
            )

            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
}