github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/monitoring/rpc_stats_interceptor.go (about)

     1  // Copyright 2016 Google Inc. All Rights Reserved.
     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 monitoring provides monitoring functionality.
    16  package monitoring
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/google/trillian/util"
    24  	"go.opencensus.io/trace"
    25  	"google.golang.org/grpc"
    26  )
    27  
    28  const traceSpanRoot = "github.com/google/trillian/monitoring.RPCStatsInterceptor"
    29  
    30  // RPCStatsInterceptor provides a gRPC interceptor that records statistics about the RPCs passing through it.
    31  type RPCStatsInterceptor struct {
    32  	prefix            string
    33  	timeSource        util.TimeSource
    34  	ReqCount          Counter
    35  	ReqSuccessCount   Counter
    36  	ReqSuccessLatency Histogram
    37  	ReqErrorCount     Counter
    38  	ReqErrorLatency   Histogram
    39  }
    40  
    41  // NewRPCStatsInterceptor creates a new RPCStatsInterceptor for the given application/component, with
    42  // a specified time source.
    43  func NewRPCStatsInterceptor(timeSource util.TimeSource, prefix string, mf MetricFactory) *RPCStatsInterceptor {
    44  	interceptor := RPCStatsInterceptor{
    45  		prefix:            prefix,
    46  		timeSource:        timeSource,
    47  		ReqCount:          mf.NewCounter(prefixedName(prefix, "rpc_requests"), "Number of requests", "method"),
    48  		ReqSuccessCount:   mf.NewCounter(prefixedName(prefix, "rpc_success"), "Number of successful requests", "method"),
    49  		ReqSuccessLatency: mf.NewHistogram(prefixedName(prefix, "rpc_success_latency"), "Latency of successful requests in seconds", "method"),
    50  		ReqErrorCount:     mf.NewCounter(prefixedName(prefix, "rpc_errors"), "Number of errored requests", "method"),
    51  		ReqErrorLatency:   mf.NewHistogram(prefixedName(prefix, "rpc_error_latency"), "Latency of errored requests in seconds", "method"),
    52  	}
    53  	return &interceptor
    54  }
    55  
    56  func prefixedName(prefix, name string) string {
    57  	return fmt.Sprintf("%s_%s", prefix, name)
    58  }
    59  
    60  func (r *RPCStatsInterceptor) recordFailureLatency(labels []string, startTime time.Time) {
    61  	latency := util.SecondsSince(r.timeSource, startTime)
    62  	r.ReqErrorCount.Inc(labels...)
    63  	r.ReqErrorLatency.Observe(latency, labels...)
    64  }
    65  
    66  // Interceptor returns a UnaryServerInterceptor that can be registered with an RPC server and
    67  // will record request counts / errors and latencies for that servers handlers
    68  func (r *RPCStatsInterceptor) Interceptor() grpc.UnaryServerInterceptor {
    69  	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    70  		labels := []string{info.FullMethod}
    71  
    72  		// This interceptor wraps the request handler so we should track the
    73  		// additional latency it imposes.
    74  		ctx, span := trace.StartSpan(ctx, traceSpanRoot)
    75  		defer span.End()
    76  
    77  		// Increase the request count for the method and start the clock
    78  		r.ReqCount.Inc(labels...)
    79  		startTime := r.timeSource.Now()
    80  
    81  		defer func() {
    82  			if rec := recover(); rec != nil {
    83  				// If we reach here then the handler exited via panic, count it as a server failure
    84  				r.recordFailureLatency(labels, startTime)
    85  				panic(rec)
    86  			}
    87  		}()
    88  
    89  		// Invoke the actual operation
    90  		rsp, err := handler(ctx, req)
    91  
    92  		// Record success / failure and latency
    93  		if err != nil {
    94  			r.recordFailureLatency(labels, startTime)
    95  		} else {
    96  			latency := util.SecondsSince(r.timeSource, startTime)
    97  			r.ReqSuccessCount.Inc(labels...)
    98  			r.ReqSuccessLatency.Observe(latency, labels...)
    99  		}
   100  
   101  		// Pass the result of the handler invocation back
   102  		return rsp, err
   103  	}
   104  }