synapsecns/sanguine

View on GitHub
services/rfq/relayer/service/otel.go

Summary

Maintainability
A
0 mins
Test Coverage
package service

import (
    "context"
    "fmt"

    "github.com/synapsecns/sanguine/services/rfq/relayer/reldb"

    "github.com/cornelk/hashmap"
    "github.com/synapsecns/sanguine/core/metrics"
    "github.com/synapsecns/sanguine/ethergo/signer/signer"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/metric"
)

const meterName = "github.com/synapsecns/sanguine/services/rfq/relayer/service"

// generate an interface for otelRecorder that exports the public method.
// this allows us to avoid using recordX externally anad makes the package less confusing.
//
// =============================================================================
// =============================================================================
// IMPORTANT: DO NOT REMOVE THIS COMMENT.
// NOTICE: PLEASE MAKE SURE YOU UPDATE BOTH THE DOCS AND THE GRAFANA DASHBOARD (IF NEEDED) AFTER UPDATING METRICS.
// =============================================================================
// =============================================================================
//
//go:generate go run github.com/vburenin/ifacemaker -f otel.go -s otelRecorder -i iOtelRecorder -p service -o otel_generated.go -c "autogenerated file"
type otelRecorder struct {
    metrics metrics.Handler
    // meter is the metrics meter.
    meter metric.Meter
    // db is the relayer database.
    db reldb.Service
    // statusCountGauge is the gauge for the status.
    statusCountGauge metric.Int64ObservableGauge
    // rebalanceCountGauge is the gauge for the rebalances.
    rebalanceCountGauge metric.Int64ObservableGauge
    // statusCounts is used for metrics.
    // status -> count
    statusCounts *hashmap.Map[reldb.QuoteRequestStatus, int]
    // signer is the signer for signing transactions.
    signer signer.Signer
}

func newOtelRecorder(meterHandler metrics.Handler, db reldb.Service, signer signer.Signer) (_ iOtelRecorder, err error) {
    or := otelRecorder{
        metrics:      meterHandler,
        db:           db,
        meter:        meterHandler.Meter(meterName),
        statusCounts: hashmap.New[reldb.QuoteRequestStatus, int](),
        signer:       signer,
    }

    or.statusCountGauge, err = or.meter.Int64ObservableGauge("status_count")
    if err != nil {
        return nil, fmt.Errorf("could not create status count gauge: %w", err)
    }

    or.rebalanceCountGauge, err = or.meter.Int64ObservableGauge("rebalance_count")
    if err != nil {
        return nil, fmt.Errorf("could not create rebalance count gauge: %w", err)
    }

    _, err = or.meter.RegisterCallback(or.recordStatusCounts, or.statusCountGauge)
    if err != nil {
        return nil, fmt.Errorf("could not register callback for status count gauge: %w", err)
    }

    _, err = or.meter.RegisterCallback(or.recordRebalanceCounts, or.rebalanceCountGauge)
    if err != nil {
        return nil, fmt.Errorf("could not register callback for rebalance count gauge: %w", err)
    }

    return &or, nil
}

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

    o.statusCounts.Range(func(status reldb.QuoteRequestStatus, count int) bool {
        opts := metric.WithAttributes(
            attribute.Int("status_int", int(status.Int())),
            attribute.String("status", status.String()),
            attribute.String("wallet", o.signer.Address().Hex()),
        )
        observer.ObserveInt64(o.statusCountGauge, int64(count), opts)

        return true
    })

    return nil
}

func (o *otelRecorder) recordRebalanceCounts(ctx context.Context, observer metric.Observer) (err error) {
    if o.metrics == nil || o.rebalanceCountGauge == nil || o.db == nil {
        return nil
    }

    rebalances, err := o.db.GetPendingRebalances(ctx)
    if err != nil {
        return fmt.Errorf("could not get pending rebalances: %w", err)
    }

    rebalanceIDs := []string{}
    for _, r := range rebalances {
        if r.RebalanceID != nil {
            rebalanceIDs = append(rebalanceIDs, *r.RebalanceID)
        }
    }

    opts := metric.WithAttributes(
        attribute.String("wallet", o.signer.Address().Hex()),
        attribute.StringSlice("rebalance_ids", rebalanceIDs),
    )
    observer.ObserveInt64(o.rebalanceCountGauge, int64(len(rebalances)), opts)

    return nil
}

// RecordStatusCount records the request status count.
func (o *otelRecorder) RecordStatusCount(status reldb.QuoteRequestStatus, count int) {
    o.statusCounts.Set(status, count)
}