| // Copyright 2016 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package grpcmon |
| |
| import ( |
| "context" |
| "fmt" |
| "time" |
| |
| gcode "google.golang.org/genproto/googleapis/rpc/code" |
| "google.golang.org/grpc" |
| "google.golang.org/grpc/codes" |
| |
| "go.chromium.org/luci/common/clock" |
| "go.chromium.org/luci/common/tsmon/distribution" |
| "go.chromium.org/luci/common/tsmon/field" |
| "go.chromium.org/luci/common/tsmon/metric" |
| "go.chromium.org/luci/common/tsmon/types" |
| ) |
| |
| var ( |
| grpcServerCount = metric.NewCounter( |
| "grpc/server/count", |
| "Total number of RPCs.", |
| nil, |
| field.String("method"), // full name of the grpc method |
| field.Int("code"), // grpc.Code of the result |
| field.String("canonical_code")) // String representation of the code above |
| |
| grpcServerDuration = metric.NewCumulativeDistribution( |
| "grpc/server/duration", |
| "Distribution of server-side RPC duration (in milliseconds).", |
| &types.MetricMetadata{Units: types.Milliseconds}, |
| distribution.DefaultBucketer, |
| field.String("method"), // full name of the grpc method |
| field.Int("code"), // grpc.Code of the result |
| field.String("canonical_code")) // String representation of the code above |
| ) |
| |
| // UnaryServerInterceptor is a grpc.UnaryServerInterceptor that gathers RPC |
| // handler metrics and sends them to tsmon. |
| // |
| // It assumes the RPC context has tsmon initialized already. |
| func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { |
| started := clock.Now(ctx) |
| panicking := true |
| defer func() { |
| // We don't want to recover anything, but we want to log Internal error |
| // in case of a panic. We pray here reportServerRPCMetrics is very |
| // lightweight and it doesn't panic itself. |
| code := codes.OK |
| switch { |
| case err != nil: |
| code = grpc.Code(err) |
| case panicking: |
| code = codes.Internal |
| } |
| reportServerRPCMetrics(ctx, info.FullMethod, code, clock.Now(ctx).Sub(started)) |
| }() |
| resp, err = handler(ctx, req) |
| panicking = false // normal exit, no panic happened, disarms defer |
| return |
| } |
| |
| // reportServerRPCMetrics sends metrics after RPC handler has finished. |
| func reportServerRPCMetrics(ctx context.Context, method string, code codes.Code, dur time.Duration) { |
| canon, ok := gcode.Code_name[int32(code)] |
| if !ok { |
| canon = fmt.Sprintf("Code(%d)", int64(code)) |
| } |
| |
| grpcServerCount.Add(ctx, 1, method, int(code), canon) |
| grpcServerDuration.Add(ctx, float64(dur.Nanoseconds()/1e6), method, int(code), canon) |
| } |