github.com/newrelic/go-agent@v3.26.0+incompatible/internal_txn_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  	"testing"
     8  	"time"
     9  
    10  	"github.com/newrelic/go-agent/internal"
    11  	"github.com/newrelic/go-agent/internal/cat"
    12  	"github.com/newrelic/go-agent/internal/sysinfo"
    13  )
    14  
    15  func TestShouldSaveTrace(t *testing.T) {
    16  	for _, tc := range []struct {
    17  		name          string
    18  		expected      bool
    19  		synthetics    bool
    20  		tracerEnabled bool
    21  		collectTraces bool
    22  		duration      time.Duration
    23  		threshold     time.Duration
    24  	}{
    25  		{
    26  			name:          "insufficient duration, all disabled",
    27  			expected:      false,
    28  			synthetics:    false,
    29  			tracerEnabled: false,
    30  			collectTraces: false,
    31  			duration:      1 * time.Second,
    32  			threshold:     2 * time.Second,
    33  		},
    34  		{
    35  			name:          "insufficient duration, only synthetics enabled",
    36  			expected:      false,
    37  			synthetics:    true,
    38  			tracerEnabled: false,
    39  			collectTraces: false,
    40  			duration:      1 * time.Second,
    41  			threshold:     2 * time.Second,
    42  		},
    43  		{
    44  			name:          "insufficient duration, only tracer enabled",
    45  			expected:      false,
    46  			synthetics:    false,
    47  			tracerEnabled: true,
    48  			collectTraces: false,
    49  			duration:      1 * time.Second,
    50  			threshold:     2 * time.Second,
    51  		},
    52  		{
    53  			name:          "insufficient duration, only collect traces enabled",
    54  			expected:      false,
    55  			synthetics:    false,
    56  			tracerEnabled: false,
    57  			collectTraces: true,
    58  			duration:      1 * time.Second,
    59  			threshold:     2 * time.Second,
    60  		},
    61  		{
    62  			name:          "insufficient duration, all normal flags enabled",
    63  			expected:      false,
    64  			synthetics:    false,
    65  			tracerEnabled: true,
    66  			collectTraces: true,
    67  			duration:      1 * time.Second,
    68  			threshold:     2 * time.Second,
    69  		},
    70  		{
    71  			name:          "insufficient duration, all flags enabled",
    72  			expected:      true,
    73  			synthetics:    true,
    74  			tracerEnabled: true,
    75  			collectTraces: true,
    76  			duration:      1 * time.Second,
    77  			threshold:     2 * time.Second,
    78  		},
    79  		{
    80  			name:          "sufficient duration, all disabled",
    81  			expected:      false,
    82  			synthetics:    false,
    83  			tracerEnabled: false,
    84  			collectTraces: false,
    85  			duration:      3 * time.Second,
    86  			threshold:     2 * time.Second,
    87  		},
    88  		{
    89  			name:          "sufficient duration, only synthetics enabled",
    90  			expected:      false,
    91  			synthetics:    true,
    92  			tracerEnabled: false,
    93  			collectTraces: false,
    94  			duration:      3 * time.Second,
    95  			threshold:     2 * time.Second,
    96  		},
    97  		{
    98  			name:          "sufficient duration, only tracer enabled",
    99  			expected:      false,
   100  			synthetics:    false,
   101  			tracerEnabled: true,
   102  			collectTraces: false,
   103  			duration:      3 * time.Second,
   104  			threshold:     2 * time.Second,
   105  		},
   106  		{
   107  			name:          "sufficient duration, only collect traces enabled",
   108  			expected:      false,
   109  			synthetics:    false,
   110  			tracerEnabled: false,
   111  			collectTraces: true,
   112  			duration:      3 * time.Second,
   113  			threshold:     2 * time.Second,
   114  		},
   115  		{
   116  			name:          "sufficient duration, all normal flags enabled",
   117  			expected:      true,
   118  			synthetics:    false,
   119  			tracerEnabled: true,
   120  			collectTraces: true,
   121  			duration:      3 * time.Second,
   122  			threshold:     2 * time.Second,
   123  		},
   124  		{
   125  			name:          "sufficient duration, all flags enabled",
   126  			expected:      true,
   127  			synthetics:    true,
   128  			tracerEnabled: true,
   129  			collectTraces: true,
   130  			duration:      3 * time.Second,
   131  			threshold:     2 * time.Second,
   132  		},
   133  	} {
   134  		txn := &txn{}
   135  
   136  		cfg := NewConfig("my app", "0123456789012345678901234567890123456789")
   137  		cfg.TransactionTracer.Enabled = tc.tracerEnabled
   138  		cfg.TransactionTracer.Threshold.Duration = tc.threshold
   139  		cfg.TransactionTracer.Threshold.IsApdexFailing = false
   140  		reply := internal.ConnectReplyDefaults()
   141  		reply.CollectTraces = tc.collectTraces
   142  		txn.appRun = newAppRun(cfg, reply)
   143  
   144  		txn.Duration = tc.duration
   145  		if tc.synthetics {
   146  			txn.CrossProcess.Synthetics = &cat.SyntheticsHeader{}
   147  			txn.CrossProcess.SetSynthetics(tc.synthetics)
   148  		}
   149  
   150  		if actual := txn.shouldSaveTrace(); actual != tc.expected {
   151  			t.Errorf("%s: unexpected shouldSaveTrace value; expected %v; got %v", tc.name, tc.expected, actual)
   152  		}
   153  	}
   154  }
   155  
   156  func TestLazilyCalculateSampledTrue(t *testing.T) {
   157  	tx := &txn{}
   158  	tx.appRun = &appRun{}
   159  	tx.BetterCAT.Priority = 0.5
   160  	tx.sampledCalculated = false
   161  	tx.BetterCAT.Enabled = true
   162  	tx.Reply = &internal.ConnectReply{
   163  		AdaptiveSampler: internal.SampleEverything{},
   164  	}
   165  	out := tx.lazilyCalculateSampled()
   166  	if !out || !tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 1.5 {
   167  		t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority)
   168  	}
   169  	tx.Reply.AdaptiveSampler = internal.SampleNothing{}
   170  	out = tx.lazilyCalculateSampled()
   171  	if !out || !tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 1.5 {
   172  		t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority)
   173  	}
   174  }
   175  
   176  func TestLazilyCalculateSampledFalse(t *testing.T) {
   177  	tx := &txn{}
   178  	tx.appRun = &appRun{}
   179  	tx.BetterCAT.Priority = 0.5
   180  	tx.sampledCalculated = false
   181  	tx.BetterCAT.Enabled = true
   182  	tx.Reply = &internal.ConnectReply{
   183  		AdaptiveSampler: internal.SampleNothing{},
   184  	}
   185  	out := tx.lazilyCalculateSampled()
   186  	if out || tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 {
   187  		t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority)
   188  	}
   189  	tx.Reply.AdaptiveSampler = internal.SampleEverything{}
   190  	out = tx.lazilyCalculateSampled()
   191  	if out || tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 {
   192  		t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority)
   193  	}
   194  }
   195  
   196  func TestLazilyCalculateSampledCATDisabled(t *testing.T) {
   197  	tx := &txn{}
   198  	tx.appRun = &appRun{}
   199  	tx.BetterCAT.Priority = 0.5
   200  	tx.sampledCalculated = false
   201  	tx.BetterCAT.Enabled = false
   202  	tx.Reply = &internal.ConnectReply{
   203  		AdaptiveSampler: internal.SampleEverything{},
   204  	}
   205  	out := tx.lazilyCalculateSampled()
   206  	if out || tx.BetterCAT.Sampled || tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 {
   207  		t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority)
   208  	}
   209  	out = tx.lazilyCalculateSampled()
   210  	if out || tx.BetterCAT.Sampled || tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 {
   211  		t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority)
   212  	}
   213  }
   214  
   215  type expectTxnTimes struct {
   216  	txn       *txn
   217  	testName  string
   218  	start     time.Time
   219  	stop      time.Time
   220  	duration  time.Duration
   221  	totalTime time.Duration
   222  }
   223  
   224  func TestTransactionDurationTotalTime(t *testing.T) {
   225  	// These tests touch internal txn structures rather than the public API:
   226  	// Testing duration and total time is tough because our API functions do
   227  	// not take fixed times.
   228  	start := time.Now()
   229  	testTxnTimes := func(expect expectTxnTimes) {
   230  		if expect.txn.Start != expect.start {
   231  			t.Error("start time", expect.testName, expect.txn.Start, expect.start)
   232  		}
   233  		if expect.txn.Stop != expect.stop {
   234  			t.Error("stop time", expect.testName, expect.txn.Stop, expect.stop)
   235  		}
   236  		if expect.txn.Duration != expect.duration {
   237  			t.Error("duration", expect.testName, expect.txn.Duration, expect.duration)
   238  		}
   239  		if expect.txn.TotalTime != expect.totalTime {
   240  			t.Error("total time", expect.testName, expect.txn.TotalTime, expect.totalTime)
   241  		}
   242  	}
   243  
   244  	// Basic transaction with no async activity.
   245  	tx := &txn{}
   246  	tx.markStart(start)
   247  	segmentStart := internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(1*time.Second))
   248  	internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(2*time.Second), "name")
   249  	tx.markEnd(start.Add(3*time.Second), &tx.mainThread)
   250  	testTxnTimes(expectTxnTimes{
   251  		txn:       tx,
   252  		testName:  "basic transaction",
   253  		start:     start,
   254  		stop:      start.Add(3 * time.Second),
   255  		duration:  3 * time.Second,
   256  		totalTime: 3 * time.Second,
   257  	})
   258  
   259  	// Transaction with async activity.
   260  	tx = &txn{}
   261  	tx.markStart(start)
   262  	segmentStart = internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(1*time.Second))
   263  	internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(2*time.Second), "name")
   264  	asyncThread := createThread(tx)
   265  	asyncSegmentStart := internal.StartSegment(&tx.TxnData, asyncThread, start.Add(1*time.Second))
   266  	internal.EndBasicSegment(&tx.TxnData, asyncThread, asyncSegmentStart, start.Add(2*time.Second), "name")
   267  	tx.markEnd(start.Add(3*time.Second), &tx.mainThread)
   268  	testTxnTimes(expectTxnTimes{
   269  		txn:       tx,
   270  		testName:  "transaction with async activity",
   271  		start:     start,
   272  		stop:      start.Add(3 * time.Second),
   273  		duration:  3 * time.Second,
   274  		totalTime: 4 * time.Second,
   275  	})
   276  
   277  	// Transaction ended on async thread.
   278  	tx = &txn{}
   279  	tx.markStart(start)
   280  	segmentStart = internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(1*time.Second))
   281  	internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(2*time.Second), "name")
   282  	asyncThread = createThread(tx)
   283  	asyncSegmentStart = internal.StartSegment(&tx.TxnData, asyncThread, start.Add(1*time.Second))
   284  	internal.EndBasicSegment(&tx.TxnData, asyncThread, asyncSegmentStart, start.Add(2*time.Second), "name")
   285  	tx.markEnd(start.Add(3*time.Second), asyncThread)
   286  	testTxnTimes(expectTxnTimes{
   287  		txn:       tx,
   288  		testName:  "transaction ended on async thread",
   289  		start:     start,
   290  		stop:      start.Add(3 * time.Second),
   291  		duration:  3 * time.Second,
   292  		totalTime: 4 * time.Second,
   293  	})
   294  
   295  	// Duration exceeds TotalTime.
   296  	tx = &txn{}
   297  	tx.markStart(start)
   298  	segmentStart = internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(0*time.Second))
   299  	internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(1*time.Second), "name")
   300  	asyncThread = createThread(tx)
   301  	asyncSegmentStart = internal.StartSegment(&tx.TxnData, asyncThread, start.Add(2*time.Second))
   302  	internal.EndBasicSegment(&tx.TxnData, asyncThread, asyncSegmentStart, start.Add(3*time.Second), "name")
   303  	tx.markEnd(start.Add(3*time.Second), asyncThread)
   304  	testTxnTimes(expectTxnTimes{
   305  		txn:       tx,
   306  		testName:  "TotalTime should be at least Duration",
   307  		start:     start,
   308  		stop:      start.Add(3 * time.Second),
   309  		duration:  3 * time.Second,
   310  		totalTime: 3 * time.Second,
   311  	})
   312  }
   313  
   314  func TestGetTraceMetadataDistributedTracingDisabled(t *testing.T) {
   315  	replyfn := func(reply *internal.ConnectReply) {
   316  		reply.AdaptiveSampler = internal.SampleEverything{}
   317  	}
   318  	cfgfn := func(cfg *Config) {
   319  		cfg.DistributedTracer.Enabled = false
   320  	}
   321  	app := testApp(replyfn, cfgfn, t)
   322  	txn := app.StartTransaction("hello", nil, nil)
   323  	metadata := txn.GetTraceMetadata()
   324  	if metadata.SpanID != "" {
   325  		t.Error(metadata.SpanID)
   326  	}
   327  	if metadata.TraceID != "" {
   328  		t.Error(metadata.TraceID)
   329  	}
   330  }
   331  
   332  func TestGetTraceMetadataSuccess(t *testing.T) {
   333  	replyfn := func(reply *internal.ConnectReply) {
   334  		reply.AdaptiveSampler = internal.SampleEverything{}
   335  		reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345)
   336  	}
   337  	cfgfn := func(cfg *Config) {
   338  		cfg.DistributedTracer.Enabled = true
   339  	}
   340  	app := testApp(replyfn, cfgfn, t)
   341  	txn := app.StartTransaction("hello", nil, nil)
   342  	metadata := txn.GetTraceMetadata()
   343  	if metadata.SpanID != "bcfb32e050b264b8" {
   344  		t.Error(metadata.SpanID)
   345  	}
   346  	if metadata.TraceID != "d9466896a525ccbf" {
   347  		t.Error(metadata.TraceID)
   348  	}
   349  	StartSegment(txn, "name")
   350  	// Span id should be different now that a segment has started.
   351  	metadata = txn.GetTraceMetadata()
   352  	if metadata.SpanID != "0e97aeb2f79d5d27" {
   353  		t.Error(metadata.SpanID)
   354  	}
   355  	if metadata.TraceID != "d9466896a525ccbf" {
   356  		t.Error(metadata.TraceID)
   357  	}
   358  }
   359  
   360  func TestGetTraceMetadataEnded(t *testing.T) {
   361  	// Test that GetTraceMetadata returns empty strings if the transaction
   362  	// has been finished.
   363  	replyfn := func(reply *internal.ConnectReply) {
   364  		reply.AdaptiveSampler = internal.SampleEverything{}
   365  		reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345)
   366  	}
   367  	cfgfn := func(cfg *Config) {
   368  		cfg.DistributedTracer.Enabled = true
   369  	}
   370  	app := testApp(replyfn, cfgfn, t)
   371  	txn := app.StartTransaction("hello", nil, nil)
   372  	txn.End()
   373  	metadata := txn.GetTraceMetadata()
   374  	if metadata.SpanID != "" {
   375  		t.Error(metadata.SpanID)
   376  	}
   377  	if metadata.TraceID != "" {
   378  		t.Error(metadata.TraceID)
   379  	}
   380  }
   381  
   382  func TestGetTraceMetadataNotSampled(t *testing.T) {
   383  	replyfn := func(reply *internal.ConnectReply) {
   384  		reply.AdaptiveSampler = internal.SampleNothing{}
   385  		reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345)
   386  	}
   387  	cfgfn := func(cfg *Config) {
   388  		cfg.DistributedTracer.Enabled = true
   389  	}
   390  	app := testApp(replyfn, cfgfn, t)
   391  	txn := app.StartTransaction("hello", nil, nil)
   392  	metadata := txn.GetTraceMetadata()
   393  	if metadata.SpanID != "" {
   394  		t.Error(metadata.SpanID)
   395  	}
   396  	if metadata.TraceID != "d9466896a525ccbf" {
   397  		t.Error(metadata.TraceID)
   398  	}
   399  }
   400  
   401  func TestGetTraceMetadataSpanEventsDisabled(t *testing.T) {
   402  	replyfn := func(reply *internal.ConnectReply) {
   403  		reply.AdaptiveSampler = internal.SampleEverything{}
   404  		reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345)
   405  	}
   406  	cfgfn := func(cfg *Config) {
   407  		cfg.DistributedTracer.Enabled = true
   408  		cfg.SpanEvents.Enabled = false
   409  	}
   410  	app := testApp(replyfn, cfgfn, t)
   411  	txn := app.StartTransaction("hello", nil, nil)
   412  	metadata := txn.GetTraceMetadata()
   413  	if metadata.SpanID != "" {
   414  		t.Error(metadata.SpanID)
   415  	}
   416  	if metadata.TraceID != "d9466896a525ccbf" {
   417  		t.Error(metadata.TraceID)
   418  	}
   419  }
   420  
   421  func TestGetTraceMetadataInboundPayload(t *testing.T) {
   422  	replyfn := func(reply *internal.ConnectReply) {
   423  		reply.AdaptiveSampler = internal.SampleEverything{}
   424  		reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345)
   425  		reply.AccountID = "account-id"
   426  		reply.TrustedAccountKey = "trust-key"
   427  		reply.PrimaryAppID = "app-id"
   428  	}
   429  	cfgfn := func(cfg *Config) {
   430  		cfg.DistributedTracer.Enabled = true
   431  	}
   432  	app := testApp(replyfn, cfgfn, t)
   433  	payload := app.StartTransaction("hello", nil, nil).CreateDistributedTracePayload()
   434  	p := payload.(internal.Payload)
   435  	p.TracedID = "trace-id"
   436  
   437  	txn := app.StartTransaction("hello", nil, nil)
   438  	err := txn.AcceptDistributedTracePayload(TransportHTTP, p)
   439  	if nil != err {
   440  		t.Error(err)
   441  	}
   442  	metadata := txn.GetTraceMetadata()
   443  	if metadata.SpanID != "9d2c19bd03daf755" {
   444  		t.Error(metadata.SpanID)
   445  	}
   446  	if metadata.TraceID != "trace-id" {
   447  		t.Error(metadata.TraceID)
   448  	}
   449  }
   450  
   451  func TestGetLinkingMetadata(t *testing.T) {
   452  	replyfn := func(reply *internal.ConnectReply) {
   453  		reply.AdaptiveSampler = internal.SampleEverything{}
   454  		reply.EntityGUID = "entities-are-guid"
   455  		reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345)
   456  	}
   457  	cfgfn := func(cfg *Config) {
   458  		cfg.AppName = "app-name"
   459  		cfg.DistributedTracer.Enabled = true
   460  	}
   461  	app := testApp(replyfn, cfgfn, t)
   462  	txn := app.StartTransaction("hello", nil, nil)
   463  
   464  	metadata := txn.GetLinkingMetadata()
   465  	host, _ := sysinfo.Hostname()
   466  	if metadata.TraceID != "d9466896a525ccbf" {
   467  		t.Error("wrong TraceID:", metadata.TraceID)
   468  	}
   469  	if metadata.SpanID != "bcfb32e050b264b8" {
   470  		t.Error("wrong SpanID:", metadata.SpanID)
   471  	}
   472  	if metadata.EntityName != "app-name" {
   473  		t.Error("wrong EntityName:", metadata.EntityName)
   474  	}
   475  	if metadata.EntityType != "SERVICE" {
   476  		t.Error("wrong EntityType:", metadata.EntityType)
   477  	}
   478  	if metadata.EntityGUID != "entities-are-guid" {
   479  		t.Error("wrong EntityGUID:", metadata.EntityGUID)
   480  	}
   481  	if metadata.Hostname != host {
   482  		t.Error("wrong Hostname:", metadata.Hostname)
   483  	}
   484  }
   485  
   486  func TestGetLinkingMetadataAppNames(t *testing.T) {
   487  	testcases := []struct {
   488  		appName  string
   489  		expected string
   490  	}{
   491  		{appName: "one-name", expected: "one-name"},
   492  		{appName: "one-name;two-name;three-name", expected: "one-name"},
   493  		{appName: "", expected: ""},
   494  	}
   495  
   496  	for _, test := range testcases {
   497  		cfgfn := func(cfg *Config) {
   498  			cfg.AppName = test.appName
   499  		}
   500  		app := testApp(nil, cfgfn, t)
   501  		txn := app.StartTransaction("hello", nil, nil)
   502  
   503  		metadata := txn.GetLinkingMetadata()
   504  		if metadata.EntityName != test.expected {
   505  			t.Errorf("wrong EntityName, actual=%s expected=%s", metadata.EntityName, test.expected)
   506  		}
   507  	}
   508  }
   509  
   510  func TestIsSampledFalse(t *testing.T) {
   511  	replyfn := func(reply *internal.ConnectReply) {
   512  		reply.AdaptiveSampler = internal.SampleNothing{}
   513  	}
   514  	cfgfn := func(cfg *Config) {
   515  		cfg.DistributedTracer.Enabled = true
   516  	}
   517  	app := testApp(replyfn, cfgfn, t)
   518  	txn := app.StartTransaction("hello", nil, nil)
   519  	sampled := txn.IsSampled()
   520  	if sampled == true {
   521  		t.Error("txn should not be sampled")
   522  	}
   523  }
   524  
   525  func TestIsSampledTrue(t *testing.T) {
   526  	replyfn := func(reply *internal.ConnectReply) {
   527  		reply.AdaptiveSampler = internal.SampleEverything{}
   528  	}
   529  	cfgfn := func(cfg *Config) {
   530  		cfg.DistributedTracer.Enabled = true
   531  	}
   532  	app := testApp(replyfn, cfgfn, t)
   533  	txn := app.StartTransaction("hello", nil, nil)
   534  	sampled := txn.IsSampled()
   535  	if sampled == false {
   536  		t.Error("txn should be sampled")
   537  	}
   538  }
   539  
   540  func TestIsSampledEnded(t *testing.T) {
   541  	// Test that Transaction.IsSampled returns false if the transaction has
   542  	// already ended.
   543  	replyfn := func(reply *internal.ConnectReply) {
   544  		reply.AdaptiveSampler = internal.SampleEverything{}
   545  	}
   546  	cfgfn := func(cfg *Config) {
   547  		cfg.DistributedTracer.Enabled = true
   548  	}
   549  	app := testApp(replyfn, cfgfn, t)
   550  	txn := app.StartTransaction("hello", nil, nil)
   551  	txn.End()
   552  	sampled := txn.IsSampled()
   553  	if sampled == true {
   554  		t.Error("finished txn should not be sampled")
   555  	}
   556  }