github.com/newrelic/go-agent@v3.26.0+incompatible/app_run_test.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package newrelic
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"reflect"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/newrelic/go-agent/internal"
    14  )
    15  
    16  func TestResponseCodeIsError(t *testing.T) {
    17  	cfg := NewConfig("my app", "0123456789012345678901234567890123456789")
    18  	cfg.ErrorCollector.IgnoreStatusCodes = append(cfg.ErrorCollector.IgnoreStatusCodes, 504)
    19  	run := newAppRun(cfg, internal.ConnectReplyDefaults())
    20  
    21  	for _, tc := range []struct {
    22  		Code    int
    23  		IsError bool
    24  	}{
    25  		{Code: 0, IsError: false}, // gRPC
    26  		{Code: 1, IsError: true},  // gRPC
    27  		{Code: 5, IsError: false}, // gRPC
    28  		{Code: 6, IsError: true},  // gRPC
    29  		{Code: 99, IsError: true},
    30  		{Code: 100, IsError: false},
    31  		{Code: 199, IsError: false},
    32  		{Code: 200, IsError: false},
    33  		{Code: 300, IsError: false},
    34  		{Code: 399, IsError: false},
    35  		{Code: 400, IsError: true},
    36  		{Code: 404, IsError: false},
    37  		{Code: 503, IsError: true},
    38  		{Code: 504, IsError: false},
    39  	} {
    40  		if is := run.responseCodeIsError(tc.Code); is != tc.IsError {
    41  			t.Errorf("responseCodeIsError for %d, wanted=%v got=%v",
    42  				tc.Code, tc.IsError, is)
    43  		}
    44  	}
    45  
    46  }
    47  
    48  func TestCrossAppTracingEnabled(t *testing.T) {
    49  	// CAT should be enabled by default.
    50  	cfg := NewConfig("my app", "0123456789012345678901234567890123456789")
    51  	run := newAppRun(cfg, internal.ConnectReplyDefaults())
    52  	if enabled := run.Config.CrossApplicationTracer.Enabled; !enabled {
    53  		t.Error(enabled)
    54  	}
    55  
    56  	// DT gets priority over CAT.
    57  	cfg = NewConfig("my app", "0123456789012345678901234567890123456789")
    58  	cfg.DistributedTracer.Enabled = true
    59  	cfg.CrossApplicationTracer.Enabled = true
    60  	run = newAppRun(cfg, internal.ConnectReplyDefaults())
    61  	if enabled := run.Config.CrossApplicationTracer.Enabled; enabled {
    62  		t.Error(enabled)
    63  	}
    64  
    65  	cfg = NewConfig("my app", "0123456789012345678901234567890123456789")
    66  	cfg.DistributedTracer.Enabled = false
    67  	cfg.CrossApplicationTracer.Enabled = false
    68  	run = newAppRun(cfg, internal.ConnectReplyDefaults())
    69  	if enabled := run.Config.CrossApplicationTracer.Enabled; enabled {
    70  		t.Error(enabled)
    71  	}
    72  
    73  	cfg = NewConfig("my app", "0123456789012345678901234567890123456789")
    74  	cfg.DistributedTracer.Enabled = false
    75  	cfg.CrossApplicationTracer.Enabled = true
    76  	run = newAppRun(cfg, internal.ConnectReplyDefaults())
    77  	if enabled := run.Config.CrossApplicationTracer.Enabled; !enabled {
    78  		t.Error(enabled)
    79  	}
    80  }
    81  
    82  func TestTxnTraceThreshold(t *testing.T) {
    83  	// Test that the default txn trace threshold is the failing apdex.
    84  	cfg := NewConfig("my app", "0123456789012345678901234567890123456789")
    85  	run := newAppRun(cfg, internal.ConnectReplyDefaults())
    86  	threshold := run.txnTraceThreshold(1 * time.Second)
    87  	if threshold != 4*time.Second {
    88  		t.Error(threshold)
    89  	}
    90  
    91  	// Test that the trace threshold can be assigned to a fixed value.
    92  	cfg = NewConfig("my app", "0123456789012345678901234567890123456789")
    93  	cfg.TransactionTracer.Threshold.IsApdexFailing = false
    94  	cfg.TransactionTracer.Threshold.Duration = 3 * time.Second
    95  	run = newAppRun(cfg, internal.ConnectReplyDefaults())
    96  	threshold = run.txnTraceThreshold(1 * time.Second)
    97  	if threshold != 3*time.Second {
    98  		t.Error(threshold)
    99  	}
   100  
   101  	// Test that the trace threshold can be overwritten by server-side-config.
   102  	// with "apdex_f".
   103  	cfg = NewConfig("my app", "0123456789012345678901234567890123456789")
   104  	cfg.TransactionTracer.Threshold.IsApdexFailing = false
   105  	cfg.TransactionTracer.Threshold.Duration = 3 * time.Second
   106  	reply := internal.ConnectReplyDefaults()
   107  	json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.transaction_threshold":"apdex_f"}}`), &reply)
   108  	run = newAppRun(cfg, reply)
   109  	threshold = run.txnTraceThreshold(1 * time.Second)
   110  	if threshold != 4*time.Second {
   111  		t.Error(threshold)
   112  	}
   113  
   114  	// Test that the trace threshold can be overwritten by server-side-config.
   115  	// with a numberic value.
   116  	cfg = NewConfig("my app", "0123456789012345678901234567890123456789")
   117  	reply = internal.ConnectReplyDefaults()
   118  	json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.transaction_threshold":3}}`), &reply)
   119  	run = newAppRun(cfg, reply)
   120  	threshold = run.txnTraceThreshold(1 * time.Second)
   121  	if threshold != 3*time.Second {
   122  		t.Error(threshold)
   123  	}
   124  }
   125  
   126  var cfg = NewConfig("name", "license")
   127  
   128  func TestEmptyReplyEventHarvestDefaults(t *testing.T) {
   129  	var run internal.HarvestConfigurer = newAppRun(cfg, &internal.ConnectReply{})
   130  	assertHarvestConfig(t, &run, expectHarvestConfig{
   131  		maxTxnEvents:    internal.MaxTxnEvents,
   132  		maxCustomEvents: internal.MaxCustomEvents,
   133  		maxErrorEvents:  internal.MaxErrorEvents,
   134  		maxSpanEvents:   internal.MaxSpanEvents,
   135  		periods: map[internal.HarvestTypes]time.Duration{
   136  			internal.HarvestTypesAll: 60 * time.Second,
   137  			0:                        60 * time.Second,
   138  		},
   139  	})
   140  }
   141  
   142  func TestEventHarvestFieldsAllPopulated(t *testing.T) {
   143  	reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{
   144  			"event_harvest_config": {
   145  				"report_period_ms": 5000,
   146  				"harvest_limits": {
   147  					"analytic_event_data": 1,
   148  					"custom_event_data": 2,
   149  					"span_event_data": 3,
   150  					"error_event_data": 4
   151  				}
   152  			}
   153  		}}`), internal.PreconnectReply{})
   154  	if nil != err {
   155  		t.Fatal(err)
   156  	}
   157  	var run internal.HarvestConfigurer = newAppRun(cfg, reply)
   158  	assertHarvestConfig(t, &run, expectHarvestConfig{
   159  		maxTxnEvents:    1,
   160  		maxCustomEvents: 2,
   161  		maxErrorEvents:  4,
   162  		maxSpanEvents:   3,
   163  		periods: map[internal.HarvestTypes]time.Duration{
   164  			internal.HarvestMetricsTraces: 60 * time.Second,
   165  			internal.HarvestTypesEvents:   5 * time.Second,
   166  		},
   167  	})
   168  }
   169  
   170  func TestZeroReportPeriod(t *testing.T) {
   171  	reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{
   172  			"event_harvest_config": {
   173  				"report_period_ms": 0
   174  			}
   175  		}}`), internal.PreconnectReply{})
   176  	if nil != err {
   177  		t.Fatal(err)
   178  	}
   179  	var run internal.HarvestConfigurer = newAppRun(cfg, reply)
   180  	assertHarvestConfig(t, &run, expectHarvestConfig{
   181  		maxTxnEvents:    internal.MaxTxnEvents,
   182  		maxCustomEvents: internal.MaxCustomEvents,
   183  		maxErrorEvents:  internal.MaxErrorEvents,
   184  		maxSpanEvents:   internal.MaxSpanEvents,
   185  		periods: map[internal.HarvestTypes]time.Duration{
   186  			internal.HarvestTypesAll: 60 * time.Second,
   187  			0:                        60 * time.Second,
   188  		},
   189  	})
   190  }
   191  
   192  func TestEventHarvestFieldsOnlySpanEvents(t *testing.T) {
   193  	reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{
   194  			"event_harvest_config": {
   195  				"report_period_ms": 5000,
   196  				"harvest_limits": { "span_event_data": 3 }
   197  			}}}`), internal.PreconnectReply{})
   198  	if nil != err {
   199  		t.Fatal(err)
   200  	}
   201  	var run internal.HarvestConfigurer = newAppRun(cfg, reply)
   202  	assertHarvestConfig(t, &run, expectHarvestConfig{
   203  		maxTxnEvents:    internal.MaxTxnEvents,
   204  		maxCustomEvents: internal.MaxCustomEvents,
   205  		maxErrorEvents:  internal.MaxErrorEvents,
   206  		maxSpanEvents:   3,
   207  		periods: map[internal.HarvestTypes]time.Duration{
   208  			internal.HarvestTypesAll ^ internal.HarvestSpanEvents: 60 * time.Second,
   209  			internal.HarvestSpanEvents:                            5 * time.Second,
   210  		},
   211  	})
   212  }
   213  
   214  func TestEventHarvestFieldsOnlyTxnEvents(t *testing.T) {
   215  	reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{
   216  			"event_harvest_config": {
   217  				"report_period_ms": 5000,
   218  				"harvest_limits": { "analytic_event_data": 3 }
   219  			}}}`), internal.PreconnectReply{})
   220  	if nil != err {
   221  		t.Fatal(err)
   222  	}
   223  	var run internal.HarvestConfigurer = newAppRun(cfg, reply)
   224  	assertHarvestConfig(t, &run, expectHarvestConfig{
   225  		maxTxnEvents:    3,
   226  		maxCustomEvents: internal.MaxCustomEvents,
   227  		maxErrorEvents:  internal.MaxErrorEvents,
   228  		maxSpanEvents:   internal.MaxSpanEvents,
   229  		periods: map[internal.HarvestTypes]time.Duration{
   230  			internal.HarvestTypesAll ^ internal.HarvestTxnEvents: 60 * time.Second,
   231  			internal.HarvestTxnEvents:                            5 * time.Second,
   232  		},
   233  	})
   234  }
   235  
   236  func TestEventHarvestFieldsOnlyErrorEvents(t *testing.T) {
   237  	reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{
   238  			"event_harvest_config": {
   239  				"report_period_ms": 5000,
   240  				"harvest_limits": { "error_event_data": 3 }
   241  			}}}`), internal.PreconnectReply{})
   242  	if nil != err {
   243  		t.Fatal(err)
   244  	}
   245  	var run internal.HarvestConfigurer = newAppRun(cfg, reply)
   246  	assertHarvestConfig(t, &run, expectHarvestConfig{
   247  		maxTxnEvents:    internal.MaxTxnEvents,
   248  		maxCustomEvents: internal.MaxCustomEvents,
   249  		maxErrorEvents:  3,
   250  		maxSpanEvents:   internal.MaxSpanEvents,
   251  		periods: map[internal.HarvestTypes]time.Duration{
   252  			internal.HarvestTypesAll ^ internal.HarvestErrorEvents: 60 * time.Second,
   253  			internal.HarvestErrorEvents:                            5 * time.Second,
   254  		},
   255  	})
   256  }
   257  
   258  func TestEventHarvestFieldsOnlyCustomEvents(t *testing.T) {
   259  	reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{
   260  			"event_harvest_config": {
   261  				"report_period_ms": 5000,
   262  				"harvest_limits": { "custom_event_data": 3 }
   263  			}}}`), internal.PreconnectReply{})
   264  	if nil != err {
   265  		t.Fatal(err)
   266  	}
   267  	var run internal.HarvestConfigurer = newAppRun(cfg, reply)
   268  	assertHarvestConfig(t, &run, expectHarvestConfig{
   269  		maxTxnEvents:    internal.MaxTxnEvents,
   270  		maxCustomEvents: 3,
   271  		maxErrorEvents:  internal.MaxErrorEvents,
   272  		maxSpanEvents:   internal.MaxSpanEvents,
   273  		periods: map[internal.HarvestTypes]time.Duration{
   274  			internal.HarvestTypesAll ^ internal.HarvestCustomEvents: 60 * time.Second,
   275  			internal.HarvestCustomEvents:                            5 * time.Second,
   276  		},
   277  	})
   278  }
   279  
   280  func TestConfigurableHarvestNegativeReportPeriod(t *testing.T) {
   281  	h, err := internal.ConstructConnectReply([]byte(`{"return_value":{
   282  			"event_harvest_config": {
   283  				"report_period_ms": -1
   284  			}}}`), internal.PreconnectReply{})
   285  	if nil != err {
   286  		t.Fatal(err)
   287  	}
   288  	expect := time.Duration(internal.DefaultConfigurableEventHarvestMs) * time.Millisecond
   289  	if period := h.ConfigurablePeriod(); period != expect {
   290  		t.Fatal(expect, period)
   291  	}
   292  }
   293  
   294  func TestReplyTraceIDGenerator(t *testing.T) {
   295  	// Test that the default connect reply has a populated trace id
   296  	// generator that works.
   297  	reply := internal.ConnectReplyDefaults()
   298  	id1 := reply.TraceIDGenerator.GenerateTraceID()
   299  	id2 := reply.TraceIDGenerator.GenerateTraceID()
   300  	if len(id1) != 16 || len(id2) != 16 || id1 == id2 {
   301  		t.Error(id1, id2)
   302  	}
   303  }
   304  
   305  func TestConfigurableTxnEvents_withCollResponse(t *testing.T) {
   306  	h, err := internal.ConstructConnectReply([]byte(
   307  		`{"return_value":{
   308  			"event_harvest_config": {
   309  				"report_period_ms": 10000,
   310                  "harvest_limits": {
   311               		"analytic_event_data": 15
   312                  }
   313  			}
   314          }}`), internal.PreconnectReply{})
   315  	if nil != err {
   316  		t.Fatal(err)
   317  	}
   318  	result := newAppRun(cfg, h).MaxTxnEvents()
   319  	if result != 15 {
   320  		t.Error(fmt.Sprintf("Unexpected max number of txn events, expected %d but got %d", 15, result))
   321  	}
   322  }
   323  
   324  func TestConfigurableTxnEvents_notInCollResponse(t *testing.T) {
   325  	reply, err := internal.ConstructConnectReply([]byte(
   326  		`{"return_value":{
   327  			"event_harvest_config": {
   328  				"report_period_ms": 10000
   329  			}
   330          }}`), internal.PreconnectReply{})
   331  	if nil != err {
   332  		t.Fatal(err)
   333  	}
   334  	expected := 10
   335  	cfg.TransactionEvents.MaxSamplesStored = expected
   336  	result := newAppRun(cfg, reply).MaxTxnEvents()
   337  	if result != expected {
   338  		t.Error(fmt.Sprintf("Unexpected max number of txn events, expected %d but got %d", expected, result))
   339  	}
   340  }
   341  
   342  func TestConfigurableTxnEvents_configMoreThanMax(t *testing.T) {
   343  	h, err := internal.ConstructConnectReply([]byte(
   344  		`{"return_value":{
   345  			"event_harvest_config": {
   346  				"report_period_ms": 10000
   347  			}
   348          }}`), internal.PreconnectReply{})
   349  	if nil != err {
   350  		t.Fatal(err)
   351  	}
   352  	cfg.TransactionEvents.MaxSamplesStored = internal.MaxTxnEvents + 100
   353  	result := newAppRun(cfg, h).MaxTxnEvents()
   354  	if result != internal.MaxTxnEvents {
   355  		t.Error(fmt.Sprintf("Unexpected max number of txn events, expected %d but got %d", internal.MaxTxnEvents, result))
   356  	}
   357  }
   358  
   359  type expectHarvestConfig struct {
   360  	maxTxnEvents    int
   361  	maxCustomEvents int
   362  	maxErrorEvents  int
   363  	maxSpanEvents   int
   364  	periods         map[internal.HarvestTypes]time.Duration
   365  }
   366  
   367  func assertHarvestConfig(t testing.TB, hc *internal.HarvestConfigurer, expect expectHarvestConfig) {
   368  	if h, ok := t.(interface {
   369  		Helper()
   370  	}); ok {
   371  		h.Helper()
   372  	}
   373  	if max := (*hc).MaxTxnEvents(); max != expect.maxTxnEvents {
   374  		t.Error(max, expect.maxTxnEvents)
   375  	}
   376  	if max := (*hc).MaxCustomEvents(); max != expect.maxCustomEvents {
   377  		t.Error(max, expect.maxCustomEvents)
   378  	}
   379  	if max := (*hc).MaxSpanEvents(); max != expect.maxSpanEvents {
   380  		t.Error(max, expect.maxSpanEvents)
   381  	}
   382  	if max := (*hc).MaxErrorEvents(); max != expect.maxErrorEvents {
   383  		t.Error(max, expect.maxErrorEvents)
   384  	}
   385  	if periods := (*hc).ReportPeriods(); !reflect.DeepEqual(periods, expect.periods) {
   386  		t.Error(periods, expect.periods)
   387  	}
   388  }