github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/rpc/metrics.go (about)

     1  // Copyright 2023 LiveKit, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rpc
    16  
    17  import (
    18  	"sort"
    19  	sync "sync"
    20  	"time"
    21  
    22  	"github.com/prometheus/client_golang/prometheus"
    23  	"go.uber.org/atomic"
    24  	"golang.org/x/exp/maps"
    25  
    26  	"github.com/livekit/psrpc"
    27  	"github.com/livekit/psrpc/pkg/middleware"
    28  )
    29  
    30  const (
    31  	livekitNamespace = "livekit"
    32  )
    33  
    34  type psrpcMetrics struct {
    35  	requestTime        prometheus.ObserverVec
    36  	streamSendTime     prometheus.ObserverVec
    37  	streamReceiveTotal *prometheus.CounterVec
    38  	streamCurrent      *prometheus.GaugeVec
    39  	errorTotal         *prometheus.CounterVec
    40  }
    41  
    42  var (
    43  	metricsBase struct {
    44  		mu          sync.RWMutex
    45  		initialized bool
    46  		curryLabels prometheus.Labels
    47  		psrpcMetrics
    48  	}
    49  	metrics atomic.Pointer[psrpcMetrics]
    50  )
    51  
    52  type psrpcMetricsOptions struct {
    53  	curryLabels prometheus.Labels
    54  }
    55  
    56  type PSRPCMetricsOption func(*psrpcMetricsOptions)
    57  
    58  func WithCurryLabels(labels prometheus.Labels) PSRPCMetricsOption {
    59  	return func(o *psrpcMetricsOptions) {
    60  		maps.Copy(o.curryLabels, labels)
    61  	}
    62  }
    63  
    64  func InitPSRPCStats(constLabels prometheus.Labels, opts ...PSRPCMetricsOption) {
    65  	metricsBase.mu.Lock()
    66  	if metricsBase.initialized {
    67  		metricsBase.mu.Unlock()
    68  		return
    69  	}
    70  	metricsBase.initialized = true
    71  
    72  	o := psrpcMetricsOptions{
    73  		curryLabels: prometheus.Labels{},
    74  	}
    75  	for _, opt := range opts {
    76  		opt(&o)
    77  	}
    78  
    79  	metricsBase.curryLabels = o.curryLabels
    80  	curryLabelNames := maps.Keys(o.curryLabels)
    81  	sort.Strings(curryLabelNames)
    82  
    83  	labels := append(curryLabelNames, "role", "kind", "service", "method")
    84  	streamLabels := append(curryLabelNames, "role", "service", "method")
    85  
    86  	metricsBase.requestTime = prometheus.NewHistogramVec(prometheus.HistogramOpts{
    87  		Namespace:   livekitNamespace,
    88  		Subsystem:   "psrpc",
    89  		Name:        "request_time_ms",
    90  		ConstLabels: constLabels,
    91  		Buckets:     []float64{10, 50, 100, 300, 500, 1000, 1500, 2000, 5000, 10000},
    92  	}, labels)
    93  	metricsBase.streamSendTime = prometheus.NewHistogramVec(prometheus.HistogramOpts{
    94  		Namespace:   livekitNamespace,
    95  		Subsystem:   "psrpc",
    96  		Name:        "stream_send_time_ms",
    97  		ConstLabels: constLabels,
    98  		Buckets:     []float64{10, 50, 100, 300, 500, 1000, 1500, 2000, 5000, 10000},
    99  	}, streamLabels)
   100  	metricsBase.streamReceiveTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
   101  		Namespace:   livekitNamespace,
   102  		Subsystem:   "psrpc",
   103  		Name:        "stream_receive_total",
   104  		ConstLabels: constLabels,
   105  	}, streamLabels)
   106  	metricsBase.streamCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{
   107  		Namespace:   livekitNamespace,
   108  		Subsystem:   "psrpc",
   109  		Name:        "stream_count",
   110  		ConstLabels: constLabels,
   111  	}, streamLabels)
   112  	metricsBase.errorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
   113  		Namespace:   livekitNamespace,
   114  		Subsystem:   "psrpc",
   115  		Name:        "error_total",
   116  		ConstLabels: constLabels,
   117  	}, labels)
   118  
   119  	metricsBase.mu.Unlock()
   120  
   121  	prometheus.MustRegister(metricsBase.requestTime)
   122  	prometheus.MustRegister(metricsBase.streamSendTime)
   123  	prometheus.MustRegister(metricsBase.streamReceiveTotal)
   124  	prometheus.MustRegister(metricsBase.streamCurrent)
   125  	prometheus.MustRegister(metricsBase.errorTotal)
   126  
   127  	CurryMetricLabels(o.curryLabels)
   128  }
   129  
   130  func CurryMetricLabels(labels prometheus.Labels) {
   131  	metricsBase.mu.Lock()
   132  	defer metricsBase.mu.Unlock()
   133  	if !metricsBase.initialized {
   134  		return
   135  	}
   136  
   137  	for k := range metricsBase.curryLabels {
   138  		if v, ok := labels[k]; ok {
   139  			metricsBase.curryLabels[k] = v
   140  		}
   141  	}
   142  
   143  	metrics.Store(&psrpcMetrics{
   144  		requestTime:        metricsBase.requestTime.MustCurryWith(metricsBase.curryLabels),
   145  		streamSendTime:     metricsBase.streamSendTime.MustCurryWith(metricsBase.curryLabels),
   146  		streamReceiveTotal: metricsBase.streamReceiveTotal.MustCurryWith(metricsBase.curryLabels),
   147  		streamCurrent:      metricsBase.streamCurrent.MustCurryWith(metricsBase.curryLabels),
   148  		errorTotal:         metricsBase.errorTotal.MustCurryWith(metricsBase.curryLabels),
   149  	})
   150  }
   151  
   152  var _ middleware.MetricsObserver = PSRPCMetricsObserver{}
   153  
   154  type PSRPCMetricsObserver struct{}
   155  
   156  func (o PSRPCMetricsObserver) OnUnaryRequest(role middleware.MetricRole, info psrpc.RPCInfo, duration time.Duration, err error) {
   157  	if err != nil {
   158  		metrics.Load().errorTotal.WithLabelValues(role.String(), "rpc", info.Service, info.Method).Inc()
   159  	} else if role == middleware.ClientRole {
   160  		metrics.Load().requestTime.WithLabelValues(role.String(), "rpc", info.Service, info.Method).Observe(float64(duration.Milliseconds()))
   161  	} else {
   162  		metrics.Load().requestTime.WithLabelValues(role.String(), "rpc", info.Service, info.Method).Observe(float64(duration.Milliseconds()))
   163  	}
   164  }
   165  
   166  func (o PSRPCMetricsObserver) OnMultiRequest(role middleware.MetricRole, info psrpc.RPCInfo, duration time.Duration, responseCount int, errorCount int) {
   167  	if responseCount == 0 {
   168  		metrics.Load().errorTotal.WithLabelValues(role.String(), "multirpc", info.Service, info.Method).Inc()
   169  	} else if role == middleware.ClientRole {
   170  		metrics.Load().requestTime.WithLabelValues(role.String(), "multirpc", info.Service, info.Method).Observe(float64(duration.Milliseconds()))
   171  	} else {
   172  		metrics.Load().requestTime.WithLabelValues(role.String(), "multirpc", info.Service, info.Method).Observe(float64(duration.Milliseconds()))
   173  	}
   174  }
   175  
   176  func (o PSRPCMetricsObserver) OnStreamSend(role middleware.MetricRole, info psrpc.RPCInfo, duration time.Duration, err error) {
   177  	if err != nil {
   178  		metrics.Load().errorTotal.WithLabelValues(role.String(), "stream", info.Service, info.Method).Inc()
   179  	} else {
   180  		metrics.Load().streamSendTime.WithLabelValues(role.String(), info.Service, info.Method).Observe(float64(duration.Milliseconds()))
   181  	}
   182  }
   183  
   184  func (o PSRPCMetricsObserver) OnStreamRecv(role middleware.MetricRole, info psrpc.RPCInfo, err error) {
   185  	if err != nil {
   186  		metrics.Load().errorTotal.WithLabelValues(role.String(), "stream", info.Service, info.Method).Inc()
   187  	} else {
   188  		metrics.Load().streamReceiveTotal.WithLabelValues(role.String(), info.Service, info.Method).Inc()
   189  	}
   190  }
   191  
   192  func (o PSRPCMetricsObserver) OnStreamOpen(role middleware.MetricRole, info psrpc.RPCInfo) {
   193  	metrics.Load().streamCurrent.WithLabelValues(role.String(), info.Service, info.Method).Inc()
   194  }
   195  
   196  func (o PSRPCMetricsObserver) OnStreamClose(role middleware.MetricRole, info psrpc.RPCInfo) {
   197  	metrics.Load().streamCurrent.WithLabelValues(role.String(), info.Service, info.Method).Dec()
   198  }
   199  
   200  type UnimplementedMetricsObserver struct{}
   201  
   202  func (o UnimplementedMetricsObserver) OnUnaryRequest(role middleware.MetricRole, rpcInfo psrpc.RPCInfo, duration time.Duration, err error) {
   203  }
   204  func (o UnimplementedMetricsObserver) OnMultiRequest(role middleware.MetricRole, rpcInfo psrpc.RPCInfo, duration time.Duration, responseCount int, errorCount int) {
   205  }
   206  func (o UnimplementedMetricsObserver) OnStreamSend(role middleware.MetricRole, rpcInfo psrpc.RPCInfo, duration time.Duration, err error) {
   207  }
   208  func (o UnimplementedMetricsObserver) OnStreamRecv(role middleware.MetricRole, rpcInfo psrpc.RPCInfo, err error) {
   209  }
   210  func (o UnimplementedMetricsObserver) OnStreamOpen(role middleware.MetricRole, rpcInfo psrpc.RPCInfo) {
   211  }
   212  func (o UnimplementedMetricsObserver) OnStreamClose(role middleware.MetricRole, rpcInfo psrpc.RPCInfo) {
   213  }