package autorelay import ( "errors" "github.com/libp2p/go-libp2p/p2p/metricshelper" "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client" pbv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb" "github.com/prometheus/client_golang/prometheus" ) const metricNamespace = "libp2p_autorelay" var ( status = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: metricNamespace, Name: "status", Help: "relay finder active", }) reservationsOpenedTotal = prometheus.NewCounter( prometheus.CounterOpts{ Namespace: metricNamespace, Name: "reservations_opened_total", Help: "Reservations Opened", }, ) reservationsClosedTotal = prometheus.NewCounter( prometheus.CounterOpts{ Namespace: metricNamespace, Name: "reservations_closed_total", Help: "Reservations Closed", }, ) reservationRequestsOutcomeTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: metricNamespace, Name: "reservation_requests_outcome_total", Help: "Reservation Request Outcome", }, []string{"request_type", "outcome"}, ) relayAddressesUpdatedTotal = prometheus.NewCounter( prometheus.CounterOpts{ Namespace: metricNamespace, Name: "relay_addresses_updated_total", Help: "Relay Addresses Updated Count", }, ) relayAddressesCount = prometheus.NewGauge( prometheus.GaugeOpts{ Namespace: metricNamespace, Name: "relay_addresses_count", Help: "Relay Addresses Count", }, ) candidatesCircuitV2SupportTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: metricNamespace, Name: "candidates_circuit_v2_support_total", Help: "Candidiates supporting circuit v2", }, []string{"support"}, ) candidatesTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: metricNamespace, Name: "candidates_total", Help: "Candidates Total", }, []string{"type"}, ) candLoopState = prometheus.NewGauge( prometheus.GaugeOpts{ Namespace: metricNamespace, Name: "candidate_loop_state", Help: "Candidate Loop State", }, ) scheduledWorkTime = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Namespace: metricNamespace, Name: "scheduled_work_time", Help: "Scheduled Work Times", }, []string{"work_type"}, ) desiredReservations = prometheus.NewGauge( prometheus.GaugeOpts{ Namespace: metricNamespace, Name: "desired_reservations", Help: "Desired Reservations", }, ) collectors = []prometheus.Collector{ status, reservationsOpenedTotal, reservationsClosedTotal, reservationRequestsOutcomeTotal, relayAddressesUpdatedTotal, relayAddressesCount, candidatesCircuitV2SupportTotal, candidatesTotal, candLoopState, scheduledWorkTime, desiredReservations, } ) type candidateLoopState int const ( peerSourceRateLimited candidateLoopState = iota waitingOnPeerChan waitingForTrigger stopped ) // MetricsTracer is the interface for tracking metrics for autorelay type MetricsTracer interface { RelayFinderStatus(isActive bool) ReservationEnded(cnt int) ReservationOpened(cnt int) ReservationRequestFinished(isRefresh bool, err error) RelayAddressCount(int) RelayAddressUpdated() CandidateChecked(supportsCircuitV2 bool) CandidateAdded(cnt int) CandidateRemoved(cnt int) CandidateLoopState(state candidateLoopState) ScheduledWorkUpdated(scheduledWork *scheduledWorkTimes) DesiredReservations(int) } type metricsTracer struct{} var _ MetricsTracer = &metricsTracer{} type metricsTracerSetting struct { reg prometheus.Registerer } type MetricsTracerOption func(*metricsTracerSetting) func WithRegisterer(reg prometheus.Registerer) MetricsTracerOption { return func(s *metricsTracerSetting) { if reg != nil { s.reg = reg } } } func NewMetricsTracer(opts ...MetricsTracerOption) MetricsTracer { setting := &metricsTracerSetting{reg: prometheus.DefaultRegisterer} for _, opt := range opts { opt(setting) } metricshelper.RegisterCollectors(setting.reg, collectors...) // Initialise these counters to 0 otherwise the first reservation requests aren't handled // correctly when using promql increse function reservationRequestsOutcomeTotal.WithLabelValues("refresh", "success") reservationRequestsOutcomeTotal.WithLabelValues("new", "success") candidatesCircuitV2SupportTotal.WithLabelValues("yes") candidatesCircuitV2SupportTotal.WithLabelValues("no") return &metricsTracer{} } func (mt *metricsTracer) RelayFinderStatus(isActive bool) { if isActive { status.Set(1) } else { status.Set(0) } } func (mt *metricsTracer) ReservationEnded(cnt int) { reservationsClosedTotal.Add(float64(cnt)) } func (mt *metricsTracer) ReservationOpened(cnt int) { reservationsOpenedTotal.Add(float64(cnt)) } func (mt *metricsTracer) ReservationRequestFinished(isRefresh bool, err error) { tags := metricshelper.GetStringSlice() defer metricshelper.PutStringSlice(tags) if isRefresh { *tags = append(*tags, "refresh") } else { *tags = append(*tags, "new") } *tags = append(*tags, getReservationRequestStatus(err)) reservationRequestsOutcomeTotal.WithLabelValues(*tags...).Inc() if !isRefresh && err == nil { reservationsOpenedTotal.Inc() } } func (mt *metricsTracer) RelayAddressUpdated() { relayAddressesUpdatedTotal.Inc() } func (mt *metricsTracer) RelayAddressCount(cnt int) { relayAddressesCount.Set(float64(cnt)) } func (mt *metricsTracer) CandidateChecked(supportsCircuitV2 bool) { tags := metricshelper.GetStringSlice() defer metricshelper.PutStringSlice(tags) if supportsCircuitV2 { *tags = append(*tags, "yes") } else { *tags = append(*tags, "no") } candidatesCircuitV2SupportTotal.WithLabelValues(*tags...).Inc() } func (mt *metricsTracer) CandidateAdded(cnt int) { tags := metricshelper.GetStringSlice() defer metricshelper.PutStringSlice(tags) *tags = append(*tags, "added") candidatesTotal.WithLabelValues(*tags...).Add(float64(cnt)) } func (mt *metricsTracer) CandidateRemoved(cnt int) { tags := metricshelper.GetStringSlice() defer metricshelper.PutStringSlice(tags) *tags = append(*tags, "removed") candidatesTotal.WithLabelValues(*tags...).Add(float64(cnt)) } func (mt *metricsTracer) CandidateLoopState(state candidateLoopState) { candLoopState.Set(float64(state)) } func (mt *metricsTracer) ScheduledWorkUpdated(scheduledWork *scheduledWorkTimes) { tags := metricshelper.GetStringSlice() defer metricshelper.PutStringSlice(tags) *tags = append(*tags, "allowed peer source call") scheduledWorkTime.WithLabelValues(*tags...).Set(float64(scheduledWork.nextAllowedCallToPeerSource.Unix())) *tags = (*tags)[:0] *tags = append(*tags, "reservation refresh") scheduledWorkTime.WithLabelValues(*tags...).Set(float64(scheduledWork.nextRefresh.Unix())) *tags = (*tags)[:0] *tags = append(*tags, "clear backoff") scheduledWorkTime.WithLabelValues(*tags...).Set(float64(scheduledWork.nextBackoff.Unix())) *tags = (*tags)[:0] *tags = append(*tags, "old candidate check") scheduledWorkTime.WithLabelValues(*tags...).Set(float64(scheduledWork.nextOldCandidateCheck.Unix())) } func (mt *metricsTracer) DesiredReservations(cnt int) { desiredReservations.Set(float64(cnt)) } func getReservationRequestStatus(err error) string { if err == nil { return "success" } status := "err other" var re client.ReservationError if errors.As(err, &re) { switch re.Status { case pbv2.Status_CONNECTION_FAILED: return "connection failed" case pbv2.Status_MALFORMED_MESSAGE: return "malformed message" case pbv2.Status_RESERVATION_REFUSED: return "reservation refused" case pbv2.Status_PERMISSION_DENIED: return "permission denied" case pbv2.Status_RESOURCE_LIMIT_EXCEEDED: return "resource limit exceeded" } } return status } // wrappedMetricsTracer wraps MetricsTracer and ignores all calls when mt is nil type wrappedMetricsTracer struct { mt MetricsTracer } var _ MetricsTracer = &wrappedMetricsTracer{} func (mt *wrappedMetricsTracer) RelayFinderStatus(isActive bool) { if mt.mt != nil { mt.mt.RelayFinderStatus(isActive) } } func (mt *wrappedMetricsTracer) ReservationEnded(cnt int) { if mt.mt != nil { mt.mt.ReservationEnded(cnt) } } func (mt *wrappedMetricsTracer) ReservationOpened(cnt int) { if mt.mt != nil { mt.mt.ReservationOpened(cnt) } } func (mt *wrappedMetricsTracer) ReservationRequestFinished(isRefresh bool, err error) { if mt.mt != nil { mt.mt.ReservationRequestFinished(isRefresh, err) } } func (mt *wrappedMetricsTracer) RelayAddressUpdated() { if mt.mt != nil { mt.mt.RelayAddressUpdated() } } func (mt *wrappedMetricsTracer) RelayAddressCount(cnt int) { if mt.mt != nil { mt.mt.RelayAddressCount(cnt) } } func (mt *wrappedMetricsTracer) CandidateChecked(supportsCircuitV2 bool) { if mt.mt != nil { mt.mt.CandidateChecked(supportsCircuitV2) } } func (mt *wrappedMetricsTracer) CandidateAdded(cnt int) { if mt.mt != nil { mt.mt.CandidateAdded(cnt) } } func (mt *wrappedMetricsTracer) CandidateRemoved(cnt int) { if mt.mt != nil { mt.mt.CandidateRemoved(cnt) } } func (mt *wrappedMetricsTracer) ScheduledWorkUpdated(scheduledWork *scheduledWorkTimes) { if mt.mt != nil { mt.mt.ScheduledWorkUpdated(scheduledWork) } } func (mt *wrappedMetricsTracer) DesiredReservations(cnt int) { if mt.mt != nil { mt.mt.DesiredReservations(cnt) } } func (mt *wrappedMetricsTracer) CandidateLoopState(state candidateLoopState) { if mt.mt != nil { mt.mt.CandidateLoopState(state) } }