go.uber.org/yarpc@v1.72.1/internal/observability/graph_test.go (about)

     1  // Copyright (c) 2022 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 observability
    22  
    23  import (
    24  	"context"
    25  	"testing"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"go.uber.org/net/metrics"
    29  	"go.uber.org/yarpc/api/transport"
    30  	"go.uber.org/yarpc/api/transport/transporttest"
    31  	"go.uber.org/zap"
    32  	"go.uber.org/zap/zaptest/observer"
    33  )
    34  
    35  func TestHandleWithReservedField(t *testing.T) {
    36  	root := metrics.New()
    37  	meter := root.Scope()
    38  
    39  	loggerCore, loggerObs := observer.New(zap.ErrorLevel)
    40  	logger := zap.New(loggerCore)
    41  
    42  	mw := NewMiddleware(Config{Scope: meter, Logger: logger, MetricTagsBlocklist: []string{"routing_delegate"}})
    43  
    44  	for _, rd := range []string{"rd1", "rd2"} {
    45  		req := &transport.Request{
    46  			Caller:          "caller",
    47  			Service:         "service",
    48  			Transport:       "",
    49  			Encoding:        "raw",
    50  			Procedure:       "procedure",
    51  			ShardKey:        "sk",
    52  			RoutingDelegate: rd,
    53  		}
    54  		// if "routing_delegate" is part of metricTagsBlockMap
    55  		// multiple requests with all other same field value but RoutingDelegate
    56  		// should successfully create a single metrics edge, without any error logging.
    57  		assert.NoError(t, mw.Handle(context.Background(), req, &transporttest.FakeResponseWriter{}, &fakeHandler{}))
    58  		assert.Len(t, loggerObs.All(), 0)
    59  	}
    60  }
    61  
    62  func TestMetricsTagIgnore(t *testing.T) {
    63  	req := &transport.Request{
    64  		Caller:          "caller",
    65  		Service:         "service",
    66  		Transport:       "",
    67  		Encoding:        "proto",
    68  		Procedure:       "procedure",
    69  		RoutingKey:      "rk",
    70  		RoutingDelegate: "rd",
    71  	}
    72  
    73  	tests := []struct {
    74  		desc            string
    75  		metricsToIgnore []string
    76  		expected        *metricsTagIgnore
    77  		expectedTags    metrics.Tags
    78  	}{
    79  		{
    80  			desc:     "empty ignore list",
    81  			expected: &metricsTagIgnore{}, // all fields default to false
    82  			expectedTags: metrics.Tags{
    83  				_source:          "caller",
    84  				_dest:            "service",
    85  				_transport:       "unknown",
    86  				_procedure:       "procedure",
    87  				_encoding:        "proto",
    88  				_routingKey:      "rk",
    89  				_routingDelegate: "rd",
    90  				_direction:       "inbound",
    91  				_rpcType:         "Unary",
    92  			},
    93  		},
    94  		{
    95  			desc:            "only reserved fields in ignore list",
    96  			metricsToIgnore: []string{_source, _dest, _transport, _procedure, _encoding, _routingKey, _routingDelegate, _direction, _rpcType},
    97  			expected: &metricsTagIgnore{
    98  				source:          true,
    99  				dest:            true,
   100  				transport:       true,
   101  				procedure:       true,
   102  				encoding:        true,
   103  				routingKey:      true,
   104  				routingDelegate: true,
   105  				direction:       true,
   106  				rpcType:         true,
   107  			},
   108  			expectedTags: metrics.Tags{
   109  				_source:          "__dropped__",
   110  				_dest:            "__dropped__",
   111  				_transport:       "__dropped__",
   112  				_procedure:       "__dropped__",
   113  				_encoding:        "__dropped__",
   114  				_routingKey:      "__dropped__",
   115  				_routingDelegate: "__dropped__",
   116  				_direction:       "__dropped__",
   117  				_rpcType:         "__dropped__",
   118  			},
   119  		},
   120  		{
   121  			desc:            "reserved fields and other fields in ignore list",
   122  			metricsToIgnore: []string{_source, _transport, _rpcType, "user_defined1", "user_defined2"},
   123  			expected: &metricsTagIgnore{
   124  				source:    true,
   125  				transport: true,
   126  				rpcType:   true,
   127  			},
   128  			expectedTags: metrics.Tags{
   129  				_source:          "__dropped__",
   130  				_dest:            "service",
   131  				_transport:       "__dropped__",
   132  				_procedure:       "procedure",
   133  				_encoding:        "proto",
   134  				_routingKey:      "rk",
   135  				_routingDelegate: "rd",
   136  				_direction:       "inbound",
   137  				_rpcType:         "__dropped__",
   138  			},
   139  		},
   140  	}
   141  	for _, tt := range tests {
   142  		t.Run(tt.desc, func(t *testing.T) {
   143  			actual := newMetricsTagIgnore(tt.metricsToIgnore)
   144  			assert.Equal(t, tt.expected, actual, "wrong metricsToIgnore")
   145  			actualTags := actual.tags(req, "inbound", transport.Unary)
   146  			assert.Equal(t, tt.expectedTags, actualTags, "tags mismatch")
   147  		})
   148  	}
   149  }
   150  
   151  func TestEdgeNopFallbacks(t *testing.T) {
   152  	// If we fail to create any of the metrics required for the edge, we should
   153  	// fall back to no-op implementations. The easiest way to trigger failures
   154  	// is to re-use the same Registry.
   155  	root := metrics.New()
   156  	meter := root.Scope()
   157  	req := &transport.Request{
   158  		Caller:          "caller",
   159  		Service:         "service",
   160  		Transport:       "",
   161  		Encoding:        "raw",
   162  		Procedure:       "procedure",
   163  		ShardKey:        "sk",
   164  		RoutingKey:      "rk",
   165  		RoutingDelegate: "rd",
   166  	}
   167  
   168  	// Should succeed, covered by middleware tests.
   169  	_ = newEdge(zap.NewNop(), meter, &metricsTagIgnore{}, req, string(_directionOutbound), transport.Unary)
   170  
   171  	// Should fall back to no-op metrics.
   172  	// Usage of nil metrics should not panic, should not observe changes.
   173  	e := newEdge(zap.NewNop(), meter, &metricsTagIgnore{}, req, string(_directionOutbound), transport.Unary)
   174  
   175  	e.calls.Inc()
   176  	assert.Equal(t, int64(0), e.calls.Load(), "Expected to fall back to no-op metrics.")
   177  
   178  	e.successes.Load()
   179  	assert.Equal(t, int64(0), e.successes.Load(), "Expected to fall back to no-op metrics.")
   180  
   181  	cf, err := e.callerFailures.Get()
   182  	assert.NoError(t, err, "Unexpected error getting caller failure counter")
   183  	cf.Inc()
   184  	assert.Equal(t, int64(0), cf.Load(), "Expected to fall back to no-op metrics.")
   185  
   186  	sf, err := e.serverFailures.Get()
   187  	assert.NoError(t, err, "Unexpected error getting server failure counter")
   188  	sf.Inc()
   189  	assert.Equal(t, int64(0), sf.Load(), "Expected to fall back to no-op metrics.")
   190  
   191  	e.latencies.Observe(0)
   192  	e.callerErrLatencies.Observe(0)
   193  	e.serverErrLatencies.Observe(0)
   194  }
   195  
   196  func TestUnknownIfEmpty(t *testing.T) {
   197  	tests := []struct {
   198  		transport string
   199  		expected  string
   200  	}{
   201  		{
   202  			expected: "unknown",
   203  		},
   204  		{
   205  			transport: "tchannel",
   206  			expected:  "tchannel",
   207  		},
   208  		{
   209  			transport: "http",
   210  			expected:  "http",
   211  		},
   212  		{
   213  			transport: "grpc",
   214  			expected:  "grpc",
   215  		},
   216  	}
   217  	for _, tt := range tests {
   218  		t.Run(tt.expected, func(t *testing.T) {
   219  			actual := unknownIfEmpty(tt.transport)
   220  			assert.Equal(t, tt.expected, actual, "expected: %s, got: %s", tt.transport, actual)
   221  
   222  		})
   223  	}
   224  }