github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/rpc/stats_handler_test.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package rpc
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/testutils"
    20  	"github.com/cockroachdb/cockroach/pkg/util"
    21  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    22  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    23  	"github.com/cockroachdb/cockroach/pkg/util/log"
    24  	"github.com/cockroachdb/cockroach/pkg/util/netutil"
    25  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    26  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    27  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    28  	"github.com/cockroachdb/errors"
    29  	"google.golang.org/grpc/stats"
    30  )
    31  
    32  func TestStatsHandlerBasic(t *testing.T) {
    33  	defer leaktest.AfterTest(t)()
    34  
    35  	expResults := map[string]*Stats{
    36  		"10.10.1.3:26257": {},
    37  		"10.10.1.4:26257": {},
    38  	}
    39  	var sh StatsHandler
    40  
    41  	ctx := context.Background()
    42  	ctx = sh.TagConn(ctx, &stats.ConnTagInfo{
    43  		RemoteAddr: util.NewUnresolvedAddr("tcp", "10.10.1.3:26257"),
    44  	})
    45  	sh.HandleRPC(ctx, &stats.InHeader{WireLength: 2})
    46  	sh.HandleRPC(ctx, &stats.InPayload{WireLength: 3})
    47  	sh.HandleRPC(ctx, &stats.InTrailer{WireLength: 5})
    48  	sh.HandleRPC(ctx, &stats.End{})
    49  	// Note that we must add 5 bytes here to account for an inaccuracy
    50  	// in the grpc stats computations. See the comment in stats_handler.go.
    51  	expResults["10.10.1.3:26257"].incoming += 15
    52  	expResults["10.10.1.3:26257"].count++
    53  
    54  	ctx = context.Background()
    55  	ctx = sh.TagConn(ctx, &stats.ConnTagInfo{
    56  		RemoteAddr: util.NewUnresolvedAddr("tcp", "10.10.1.4:26257"),
    57  	})
    58  	sh.HandleRPC(ctx, &stats.OutPayload{WireLength: 7})
    59  	sh.HandleRPC(ctx, &stats.OutTrailer{WireLength: 11})
    60  	expResults["10.10.1.4:26257"].outgoing += 18
    61  
    62  	cStats1 := sh.newClient("10.10.1.3:26257")
    63  	cStats1.HandleRPC(ctx, &stats.InHeader{WireLength: 13})
    64  	cStats1.HandleRPC(ctx, &stats.InPayload{WireLength: 17})
    65  	cStats1.HandleRPC(ctx, &stats.InTrailer{WireLength: 19})
    66  	// See comment above for why we must add 5 bytes here.
    67  	expResults["10.10.1.3:26257"].incoming += 54
    68  
    69  	cStats2 := sh.newClient("10.10.1.4:26257")
    70  	cStats2.HandleRPC(ctx, &stats.OutPayload{WireLength: 23})
    71  	cStats2.HandleRPC(ctx, &stats.OutTrailer{WireLength: 29})
    72  	expResults["10.10.1.4:26257"].outgoing += 52
    73  
    74  	// Verify the expected results.
    75  	sh.stats.Range(func(k, v interface{}) bool {
    76  		key := k.(string)
    77  		value := v.(*Stats)
    78  		if e, a := expResults[key].Incoming(), value.Incoming(); e != a {
    79  			t.Errorf("for target=%s, expected Incoming=%d, got %d", key, e, a)
    80  		}
    81  		if e, a := expResults[key].Outgoing(), value.Outgoing(); e != a {
    82  			t.Errorf("for target=%s, expected Outgoing=%d, got %d", key, e, a)
    83  		}
    84  		if e, a := expResults[key].Count(), value.Count(); e != a {
    85  			t.Errorf("for target=%s, expected Count=%d, got %d", key, e, a)
    86  		}
    87  		return true
    88  	})
    89  }
    90  
    91  // TestStatsHandlerWithHeartbeats verifies the stats handler captures
    92  // incoming and outgoing traffic with real server and client connections.
    93  func TestStatsHandlerWithHeartbeats(t *testing.T) {
    94  	defer leaktest.AfterTest(t)()
    95  
    96  	// Can't be zero because that'd be an empty offset.
    97  	clock := hlc.NewClock(timeutil.Unix(0, 1).UnixNano, time.Nanosecond)
    98  	stopper := stop.NewStopper()
    99  	defer stopper.Stop(context.Background())
   100  
   101  	// Shared cluster ID by all RPC peers (this ensures that the peers
   102  	// don't talk to servers from unrelated tests by accident).
   103  	clusterID := uuid.MakeV4()
   104  
   105  	serverCtx := newTestContext(clusterID, clock, stopper)
   106  	const serverNodeID = 1
   107  	serverCtx.NodeID.Set(context.Background(), serverNodeID)
   108  	s := newTestServer(t, serverCtx)
   109  
   110  	heartbeat := &ManualHeartbeatService{
   111  		ready:              make(chan error),
   112  		stopper:            stopper,
   113  		clock:              clock,
   114  		remoteClockMonitor: serverCtx.RemoteClocks,
   115  		settings:           serverCtx.settings,
   116  		nodeID:             &serverCtx.NodeID,
   117  	}
   118  	RegisterHeartbeatServer(s, heartbeat)
   119  
   120  	ln, err := netutil.ListenAndServeGRPC(serverCtx.Stopper, s, util.TestAddr)
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	remoteAddr := ln.Addr().String()
   125  
   126  	clientCtx := newTestContext(clusterID, clock, stopper)
   127  	// Make the interval shorter to speed up the test.
   128  	clientCtx.heartbeatInterval = 1 * time.Millisecond
   129  	go func() { heartbeat.ready <- nil }()
   130  	if _, err := clientCtx.GRPCDialNode(remoteAddr, serverNodeID, DefaultClass).
   131  		Connect(context.Background()); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  
   135  	// Wait for the connection & successful heartbeat.
   136  	testutils.SucceedsSoon(t, func() error {
   137  		err := clientCtx.TestingConnHealth(remoteAddr, serverNodeID)
   138  		if err != nil && !errors.Is(err, ErrNotHeartbeated) {
   139  			t.Fatal(err)
   140  		}
   141  		return err
   142  	})
   143  
   144  	// Verify server and client stats in a SucceedsSoon loop to avoid
   145  	// timing-related stats counting problems.
   146  	testutils.SucceedsSoon(t, func() error {
   147  		// Get server stats.
   148  		serverSM := serverCtx.GetStatsMap()
   149  		var serverVal interface{}
   150  		serverSM.Range(func(k, v interface{}) bool {
   151  			serverVal = v
   152  			return true
   153  		})
   154  		if serverVal == nil {
   155  			return fmt.Errorf("expected server map to contain stats for one client connection")
   156  		}
   157  		// Get client stats.
   158  		clientSM := clientCtx.GetStatsMap()
   159  		clientVal, ok := clientSM.Load(remoteAddr)
   160  		if !ok {
   161  			return fmt.Errorf("expected map to contain stats for remote addr %s", remoteAddr)
   162  		}
   163  
   164  		// Verify that server stats mirror client stats. Note that because
   165  		// GRPC is no longer reporting outgoing header wire lengths, we
   166  		// can't compare incoming and outgoing stats for equality, but are
   167  		// forced to verify one is less than the other.
   168  		if s, c := serverVal.(*Stats).Incoming(), clientVal.(*Stats).Outgoing(); s == 0 || c == 0 || s <= c {
   169  			return fmt.Errorf("expected server.incoming > client.outgoing; got %d, %d", s, c)
   170  		}
   171  		if s, c := serverVal.(*Stats).Outgoing(), clientVal.(*Stats).Incoming(); s == 0 || c == 0 || s > c {
   172  			return fmt.Errorf("expected server.outgoing < client.incoming; got %d, %d", s, c)
   173  		}
   174  		log.Infof(context.Background(), "server incoming = %v, server outgoing = %v, client incoming = %v, client outgoing = %v",
   175  			serverVal.(*Stats).Incoming(), serverVal.(*Stats).Outgoing(), clientVal.(*Stats).Incoming(), clientVal.(*Stats).Outgoing())
   176  		return nil
   177  	})
   178  }