github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/grpc/grpc_metrics_test.go (about)

     1  // Copyright (c) 2020 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package grpc
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"io"
    27  	"net"
    28  	"testing"
    29  	"time"
    30  
    31  	testpb "github.com/m3db/m3/src/x/generated/proto/test"
    32  
    33  	"github.com/stretchr/testify/require"
    34  	"github.com/uber-go/tally"
    35  	"google.golang.org/grpc"
    36  	"google.golang.org/grpc/codes"
    37  	"google.golang.org/grpc/status"
    38  )
    39  
    40  func TestInterceptors(t *testing.T) {
    41  	server, client, scope := testServerClient(t)
    42  	defer server.Close()
    43  
    44  	_, err := client.PingEmpty(context.Background(), &testpb.Empty{})
    45  	require.NoError(t, err)
    46  
    47  	_, err = client.PingError(context.Background(), &testpb.PingRequest{
    48  		ErrorCodeReturned: uint32(codes.FailedPrecondition),
    49  	})
    50  	require.Error(t, err)
    51  
    52  	ss, err := client.PingList(context.Background(), &testpb.PingRequest{})
    53  	require.NoError(t, err)
    54  
    55  	var recv int64
    56  	for {
    57  		_, err := ss.Recv()
    58  		if err == io.EOF {
    59  			break
    60  		}
    61  		require.NoError(t, err)
    62  		recv++
    63  	}
    64  
    65  	expectedCounters := map[string]int64{
    66  		"grpc_client_handled_total+grpc_code=FailedPrecondition,grpc_method=PingError,grpc_service=m3.test.TestService,grpc_type=unary": 1,
    67  		"grpc_client_handled_total+grpc_code=OK,grpc_method=PingEmpty,grpc_service=m3.test.TestService,grpc_type=unary":                 1,
    68  		"grpc_client_handled_total+grpc_code=OK,grpc_method=PingList,grpc_service=m3.test.TestService,grpc_type=server_stream":          1,
    69  		"grpc_client_msg_received_total+grpc_method=PingEmpty,grpc_service=m3.test.TestService,grpc_type=unary":                         0,
    70  		"grpc_client_msg_received_total+grpc_method=PingError,grpc_service=m3.test.TestService,grpc_type=unary":                         0,
    71  		"grpc_client_msg_received_total+grpc_method=PingList,grpc_service=m3.test.TestService,grpc_type=server_stream":                  recv,
    72  		"grpc_client_msg_sent_total+grpc_method=PingEmpty,grpc_service=m3.test.TestService,grpc_type=unary":                             0,
    73  		"grpc_client_msg_sent_total+grpc_method=PingError,grpc_service=m3.test.TestService,grpc_type=unary":                             0,
    74  		"grpc_client_msg_sent_total+grpc_method=PingList,grpc_service=m3.test.TestService,grpc_type=server_stream":                      1,
    75  		"grpc_client_started_total+grpc_method=PingEmpty,grpc_service=m3.test.TestService,grpc_type=unary":                              1,
    76  		"grpc_client_started_total+grpc_method=PingError,grpc_service=m3.test.TestService,grpc_type=unary":                              1,
    77  		"grpc_client_started_total+grpc_method=PingList,grpc_service=m3.test.TestService,grpc_type=server_stream":                       1,
    78  	}
    79  
    80  	snapshot := scope.Snapshot()
    81  
    82  	// Enable to see current values:
    83  	// for k, v := range snapshot.Counters() {
    84  	// 	fmt.Printf("\"%s\": %d,\n", k, v.Value())
    85  	// }
    86  	// for k, v := range snapshot.Histograms() {
    87  	// 	fmt.Printf("%s = %v\n", k, v.Values())
    88  	// }
    89  
    90  	for k, v := range expectedCounters {
    91  		counter, ok := snapshot.Counters()[k]
    92  		require.True(t, ok, fmt.Sprintf("metric missing: %s", k))
    93  		require.Equal(t, v, counter.Value())
    94  	}
    95  
    96  	expectedHistogramsTotalBucketCounts := map[string]int64{
    97  		"grpc_client_handling_seconds+grpc_method=PingEmpty,grpc_service=m3.test.TestService,grpc_type=unary":                 1,
    98  		"grpc_client_handling_seconds+grpc_method=PingError,grpc_service=m3.test.TestService,grpc_type=unary":                 1,
    99  		"grpc_client_handling_seconds+grpc_method=PingList,grpc_service=m3.test.TestService,grpc_type=server_stream":          1,
   100  		"grpc_client_msg_recv_handling_seconds+grpc_method=PingList,grpc_service=m3.test.TestService,grpc_type=server_stream": recv + 1,
   101  		"grpc_client_msg_send_handling_seconds+grpc_method=PingList,grpc_service=m3.test.TestService,grpc_type=server_stream": 1,
   102  	}
   103  	for k, v := range expectedHistogramsTotalBucketCounts {
   104  		histogram, ok := snapshot.Histograms()[k]
   105  		require.True(t, ok, fmt.Sprintf("metric missing: %s", k))
   106  
   107  		var actualBucketCount int64
   108  		for _, bucketCount := range histogram.Values() {
   109  			actualBucketCount += bucketCount
   110  		}
   111  		for _, bucketCount := range histogram.Durations() {
   112  			actualBucketCount += bucketCount
   113  		}
   114  		require.Equal(t, v, actualBucketCount, fmt.Sprintf("metric bucket count mismatch: %s", k))
   115  	}
   116  }
   117  
   118  func testServerClient(t *testing.T) (net.Listener, testpb.TestServiceClient, tally.TestScope) {
   119  	serverListener, err := net.Listen("tcp", "127.0.0.1:0")
   120  	require.NoError(t, err)
   121  
   122  	server := grpc.NewServer()
   123  	testpb.RegisterTestServiceServer(server, testService{})
   124  
   125  	go func() {
   126  		server.Serve(serverListener)
   127  	}()
   128  
   129  	s, _ := tally.NewRootScope(tally.ScopeOptions{Separator: "_"}, 0)
   130  	scope, ok := s.(tally.TestScope)
   131  	require.True(t, ok)
   132  
   133  	clientConn, err := grpc.Dial(
   134  		serverListener.Addr().String(),
   135  		grpc.WithInsecure(),
   136  		grpc.WithBlock(),
   137  		grpc.WithUnaryInterceptor(UnaryClientInterceptor(InterceptorInstrumentOptions{Scope: s})),
   138  		grpc.WithStreamInterceptor(StreamClientInterceptor(InterceptorInstrumentOptions{Scope: s})),
   139  		grpc.WithTimeout(2*time.Second))
   140  	require.NoError(t, err)
   141  	return serverListener, testpb.NewTestServiceClient(clientConn), scope
   142  }
   143  
   144  type testService struct {
   145  }
   146  
   147  func (s testService) PingEmpty(ctx context.Context, _ *testpb.Empty) (*testpb.PingResponse, error) {
   148  	return &testpb.PingResponse{Value: "ping response", Counter: 42}, nil
   149  }
   150  
   151  func (s testService) Ping(ctx context.Context, ping *testpb.PingRequest) (*testpb.PingResponse, error) {
   152  	return &testpb.PingResponse{Value: ping.Value, Counter: 42}, nil
   153  }
   154  
   155  func (s testService) PingError(ctx context.Context, ping *testpb.PingRequest) (*testpb.Empty, error) {
   156  	code := codes.Code(ping.ErrorCodeReturned)
   157  	return nil, status.Errorf(code, "an error")
   158  }
   159  
   160  func (s testService) PingList(ping *testpb.PingRequest, stream testpb.TestService_PingListServer) error {
   161  	if ping.ErrorCodeReturned != 0 {
   162  		return status.Errorf(codes.Code(ping.ErrorCodeReturned), "an_error")
   163  	}
   164  	for i := 0; i < 20; i++ {
   165  		stream.Send(&testpb.PingResponse{Value: ping.Value, Counter: int32(i)})
   166  	}
   167  	return nil
   168  }