go.temporal.io/server@v1.23.0/common/metrics/grpc.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package metrics 26 27 import ( 28 "context" 29 "sync" 30 31 metricspb "go.temporal.io/server/api/metrics/v1" 32 "go.temporal.io/server/common/log" 33 "go.temporal.io/server/common/log/tag" 34 35 "google.golang.org/grpc" 36 "google.golang.org/grpc/metadata" 37 ) 38 39 type ( 40 metricsContextKey struct{} 41 42 // metricsContext is used to propagate metrics across single gRPC call within server 43 metricsContext struct { 44 sync.Mutex 45 CountersInt map[string]int64 46 } 47 ) 48 49 var ( 50 // "-bin" suffix is a reserved in gRPC that signals that metadata string value is actually a byte data 51 // If trailer key has such a suffix, value will be base64 encoded. 52 metricsTrailerKey = "metrics-trailer-bin" 53 metricsCtxKey = metricsContextKey{} 54 ) 55 56 // NewServerMetricsContextInjectorInterceptor returns grpc server interceptor that adds metrics context to golang 57 // context. 58 func NewServerMetricsContextInjectorInterceptor() grpc.UnaryServerInterceptor { 59 return func( 60 ctx context.Context, 61 req interface{}, 62 info *grpc.UnaryServerInfo, 63 handler grpc.UnaryHandler, 64 ) (interface{}, error) { 65 ctxWithMetricsBaggage := AddMetricsContext(ctx) 66 return handler(ctxWithMetricsBaggage, req) 67 } 68 } 69 70 // NewClientMetricsTrailerPropagatorInterceptor returns grpc client interceptor that injects metrics received in trailer 71 // into metrics context. 72 func NewClientMetricsTrailerPropagatorInterceptor(logger log.Logger) grpc.UnaryClientInterceptor { 73 return func( 74 ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, 75 opts ...grpc.CallOption, 76 ) error { 77 var trailer metadata.MD 78 optsWithTrailer := append(opts, grpc.Trailer(&trailer)) 79 err := invoker(ctx, method, req, reply, cc, optsWithTrailer...) 80 81 baggageStrings := trailer.Get(metricsTrailerKey) 82 if len(baggageStrings) == 0 { 83 return err 84 } 85 86 for _, baggageString := range baggageStrings { 87 baggageBytes := []byte(baggageString) 88 metricsBaggage := &metricspb.Baggage{} 89 unmarshalErr := metricsBaggage.Unmarshal(baggageBytes) 90 if unmarshalErr != nil { 91 logger.Error("unable to unmarshal metrics baggage from trailer", tag.Error(unmarshalErr)) 92 continue 93 } 94 for counterName, counterValue := range metricsBaggage.CountersInt { 95 ContextCounterAdd(ctx, counterName, counterValue) 96 } 97 } 98 99 return err 100 } 101 } 102 103 // NewServerMetricsTrailerPropagatorInterceptor returns grpc server interceptor that injects metrics from context into 104 // gRPC trailer. 105 func NewServerMetricsTrailerPropagatorInterceptor(logger log.Logger) grpc.UnaryServerInterceptor { 106 return func( 107 ctx context.Context, 108 req interface{}, 109 info *grpc.UnaryServerInfo, 110 handler grpc.UnaryHandler, 111 ) (interface{}, error) { 112 // we want to return original handler response, so don't override err 113 resp, err := handler(ctx, req) 114 115 select { 116 case <-ctx.Done(): 117 return resp, err 118 default: 119 } 120 121 metricsCtx := getMetricsContext(ctx) 122 if metricsCtx == nil { 123 return resp, err 124 } 125 126 metricsBaggage := &metricspb.Baggage{CountersInt: make(map[string]int64)} 127 128 metricsCtx.Lock() 129 for k, v := range metricsCtx.CountersInt { 130 metricsBaggage.CountersInt[k] = v 131 } 132 metricsCtx.Unlock() 133 134 bytes, marshalErr := metricsBaggage.Marshal() 135 if marshalErr != nil { 136 logger.Error("unable to marshal metric baggage", tag.Error(marshalErr)) 137 } 138 139 md := metadata.Pairs(metricsTrailerKey, string(bytes)) 140 141 marshalErr = grpc.SetTrailer(ctx, md) 142 if marshalErr != nil { 143 logger.Error("unable to add metrics baggage to gRPC trailer", tag.Error(marshalErr)) 144 } 145 146 return resp, err 147 } 148 } 149 150 // getMetricsContext extracts metrics context from golang context. 151 func getMetricsContext(ctx context.Context) *metricsContext { 152 metricsCtx := ctx.Value(metricsCtxKey) 153 if metricsCtx == nil { 154 return nil 155 } 156 157 return metricsCtx.(*metricsContext) 158 } 159 160 func AddMetricsContext(ctx context.Context) context.Context { 161 metricsCtx := &metricsContext{} 162 return context.WithValue(ctx, metricsCtxKey, metricsCtx) 163 } 164 165 // ContextCounterAdd adds value to counter within metrics context. 166 func ContextCounterAdd(ctx context.Context, name string, value int64) bool { 167 metricsCtx := getMetricsContext(ctx) 168 169 if metricsCtx == nil { 170 return false 171 } 172 173 metricsCtx.Lock() 174 defer metricsCtx.Unlock() 175 176 if metricsCtx.CountersInt == nil { 177 metricsCtx.CountersInt = make(map[string]int64) 178 } 179 180 val := metricsCtx.CountersInt[name] 181 val += value 182 metricsCtx.CountersInt[name] = val 183 184 return true 185 } 186 187 // ContextCounterGet returns value and true if successfully retrieved value 188 func ContextCounterGet(ctx context.Context, name string) (int64, bool) { 189 metricsCtx := getMetricsContext(ctx) 190 191 if metricsCtx == nil { 192 return 0, false 193 } 194 195 metricsCtx.Lock() 196 defer metricsCtx.Unlock() 197 198 if metricsCtx.CountersInt == nil { 199 return 0, false 200 } 201 202 result := metricsCtx.CountersInt[name] 203 return result, true 204 }