services/rfq/relayer/service/otel.go
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)
}