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 }