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

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package internal
     5  
     6  import (
     7  	"net/http"
     8  	"net/url"
     9  	"strconv"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/newrelic/go-agent/internal/cat"
    15  	"github.com/newrelic/go-agent/internal/crossagent"
    16  	"github.com/newrelic/go-agent/internal/logger"
    17  )
    18  
    19  func TestStartEndSegment(t *testing.T) {
    20  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
    21  
    22  	txndata := &TxnData{}
    23  	thread := &Thread{}
    24  	token := StartSegment(txndata, thread, start)
    25  	stop := start.Add(1 * time.Second)
    26  	end, err := endSegment(txndata, thread, token, stop)
    27  	if nil != err {
    28  		t.Error(err)
    29  	}
    30  	if end.exclusive != end.duration {
    31  		t.Error(end.exclusive, end.duration)
    32  	}
    33  	if end.duration != 1*time.Second {
    34  		t.Error(end.duration)
    35  	}
    36  	if end.start.Time != start {
    37  		t.Error(end.start, start)
    38  	}
    39  	if end.stop.Time != stop {
    40  		t.Error(end.stop, stop)
    41  	}
    42  	if 0 != len(txndata.spanEvents) {
    43  		t.Error(txndata.spanEvents)
    44  	}
    45  }
    46  
    47  func TestMultipleChildren(t *testing.T) {
    48  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
    49  	txndata := &TxnData{}
    50  	thread := &Thread{}
    51  
    52  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
    53  	t2 := StartSegment(txndata, thread, start.Add(2*time.Second))
    54  	end2, err2 := endSegment(txndata, thread, t2, start.Add(3*time.Second))
    55  	t3 := StartSegment(txndata, thread, start.Add(4*time.Second))
    56  	end3, err3 := endSegment(txndata, thread, t3, start.Add(5*time.Second))
    57  	end1, err1 := endSegment(txndata, thread, t1, start.Add(6*time.Second))
    58  	t4 := StartSegment(txndata, thread, start.Add(7*time.Second))
    59  	end4, err4 := endSegment(txndata, thread, t4, start.Add(8*time.Second))
    60  
    61  	if nil != err1 || end1.duration != 5*time.Second || end1.exclusive != 3*time.Second {
    62  		t.Error(end1, err1)
    63  	}
    64  	if nil != err2 || end2.duration != end2.exclusive || end2.duration != time.Second {
    65  		t.Error(end2, err2)
    66  	}
    67  	if nil != err3 || end3.duration != end3.exclusive || end3.duration != time.Second {
    68  		t.Error(end3, err3)
    69  	}
    70  	if nil != err4 || end4.duration != end4.exclusive || end4.duration != time.Second {
    71  		t.Error(end4, err4)
    72  	}
    73  	if thread.TotalTime() != 7*time.Second {
    74  		t.Error(thread.TotalTime())
    75  	}
    76  }
    77  
    78  func TestInvalidStart(t *testing.T) {
    79  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
    80  	txndata := &TxnData{}
    81  	thread := &Thread{}
    82  
    83  	end, err := endSegment(txndata, thread, SegmentStartTime{}, start.Add(1*time.Second))
    84  	if err != errMalformedSegment {
    85  		t.Error(end, err)
    86  	}
    87  	StartSegment(txndata, thread, start.Add(2*time.Second))
    88  	end, err = endSegment(txndata, thread, SegmentStartTime{}, start.Add(3*time.Second))
    89  	if err != errMalformedSegment {
    90  		t.Error(end, err)
    91  	}
    92  }
    93  
    94  func TestSegmentAlreadyEnded(t *testing.T) {
    95  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
    96  	txndata := &TxnData{}
    97  	thread := &Thread{}
    98  
    99  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   100  	end, err := endSegment(txndata, thread, t1, start.Add(2*time.Second))
   101  	if err != nil {
   102  		t.Error(end, err)
   103  	}
   104  	end, err = endSegment(txndata, thread, t1, start.Add(3*time.Second))
   105  	if err != errSegmentOrder {
   106  		t.Error(end, err)
   107  	}
   108  }
   109  
   110  func TestSegmentBadStamp(t *testing.T) {
   111  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   112  	txndata := &TxnData{}
   113  	thread := &Thread{}
   114  
   115  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   116  	t1.Stamp++
   117  	end, err := endSegment(txndata, thread, t1, start.Add(2*time.Second))
   118  	if err != errSegmentOrder {
   119  		t.Error(end, err)
   120  	}
   121  }
   122  
   123  func TestSegmentBadDepth(t *testing.T) {
   124  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   125  	txndata := &TxnData{}
   126  	thread := &Thread{}
   127  
   128  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   129  	t1.Depth++
   130  	end, err := endSegment(txndata, thread, t1, start.Add(2*time.Second))
   131  	if err != errSegmentOrder {
   132  		t.Error(end, err)
   133  	}
   134  }
   135  
   136  func TestSegmentNegativeDepth(t *testing.T) {
   137  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   138  	txndata := &TxnData{}
   139  	thread := &Thread{}
   140  
   141  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   142  	t1.Depth = -1
   143  	end, err := endSegment(txndata, thread, t1, start.Add(2*time.Second))
   144  	if err != errMalformedSegment {
   145  		t.Error(end, err)
   146  	}
   147  }
   148  
   149  func TestSegmentOutOfOrder(t *testing.T) {
   150  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   151  	txndata := &TxnData{}
   152  	thread := &Thread{}
   153  
   154  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   155  	t2 := StartSegment(txndata, thread, start.Add(2*time.Second))
   156  	t3 := StartSegment(txndata, thread, start.Add(3*time.Second))
   157  	end2, err2 := endSegment(txndata, thread, t2, start.Add(4*time.Second))
   158  	end3, err3 := endSegment(txndata, thread, t3, start.Add(5*time.Second))
   159  	t4 := StartSegment(txndata, thread, start.Add(6*time.Second))
   160  	end4, err4 := endSegment(txndata, thread, t4, start.Add(7*time.Second))
   161  	end1, err1 := endSegment(txndata, thread, t1, start.Add(8*time.Second))
   162  
   163  	if nil != err1 ||
   164  		end1.duration != 7*time.Second ||
   165  		end1.exclusive != 4*time.Second {
   166  		t.Error(end1, err1)
   167  	}
   168  	if nil != err2 || end2.duration != end2.exclusive || end2.duration != 2*time.Second {
   169  		t.Error(end2, err2)
   170  	}
   171  	if err3 != errSegmentOrder {
   172  		t.Error(end3, err3)
   173  	}
   174  	if nil != err4 || end4.duration != end4.exclusive || end4.duration != 1*time.Second {
   175  		t.Error(end4, err4)
   176  	}
   177  }
   178  
   179  //                                          |-t3-|    |-t4-|
   180  //                           |-t2-|    |-never-finished----------
   181  //            |-t1-|    |--never-finished------------------------
   182  //       |-------alpha------------------------------------------|
   183  //  0    1    2    3    4    5    6    7    8    9    10   11   12
   184  func TestLostChildren(t *testing.T) {
   185  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   186  	txndata := &TxnData{}
   187  	thread := &Thread{}
   188  
   189  	alpha := StartSegment(txndata, thread, start.Add(1*time.Second))
   190  	t1 := StartSegment(txndata, thread, start.Add(2*time.Second))
   191  	EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1")
   192  	StartSegment(txndata, thread, start.Add(4*time.Second))
   193  	t2 := StartSegment(txndata, thread, start.Add(5*time.Second))
   194  	EndBasicSegment(txndata, thread, t2, start.Add(6*time.Second), "t2")
   195  	StartSegment(txndata, thread, start.Add(7*time.Second))
   196  	t3 := StartSegment(txndata, thread, start.Add(8*time.Second))
   197  	EndBasicSegment(txndata, thread, t3, start.Add(9*time.Second), "t3")
   198  	t4 := StartSegment(txndata, thread, start.Add(10*time.Second))
   199  	EndBasicSegment(txndata, thread, t4, start.Add(11*time.Second), "t4")
   200  	EndBasicSegment(txndata, thread, alpha, start.Add(12*time.Second), "alpha")
   201  
   202  	metrics := newMetricTable(100, time.Now())
   203  	txndata.FinalName = "WebTransaction/Go/zip"
   204  	txndata.IsWeb = true
   205  	MergeBreakdownMetrics(txndata, metrics)
   206  	ExpectMetrics(t, metrics, []WantMetric{
   207  		{"Custom/alpha", "", false, []float64{1, 11, 7, 11, 11, 121}},
   208  		{"Custom/t1", "", false, []float64{1, 1, 1, 1, 1, 1}},
   209  		{"Custom/t2", "", false, []float64{1, 1, 1, 1, 1, 1}},
   210  		{"Custom/t3", "", false, []float64{1, 1, 1, 1, 1, 1}},
   211  		{"Custom/t4", "", false, []float64{1, 1, 1, 1, 1, 1}},
   212  		{"Custom/alpha", txndata.FinalName, false, []float64{1, 11, 7, 11, 11, 121}},
   213  		{"Custom/t1", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   214  		{"Custom/t2", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   215  		{"Custom/t3", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   216  		{"Custom/t4", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   217  	})
   218  }
   219  
   220  //                                          |-t3-|    |-t4-|
   221  //                           |-t2-|    |-never-finished----------
   222  //            |-t1-|    |--never-finished------------------------
   223  //  |-------root-------------------------------------------------
   224  //  0    1    2    3    4    5    6    7    8    9    10   11   12
   225  func TestLostChildrenRoot(t *testing.T) {
   226  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   227  	txndata := &TxnData{}
   228  	thread := &Thread{}
   229  
   230  	t1 := StartSegment(txndata, thread, start.Add(2*time.Second))
   231  	EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1")
   232  	StartSegment(txndata, thread, start.Add(4*time.Second))
   233  	t2 := StartSegment(txndata, thread, start.Add(5*time.Second))
   234  	EndBasicSegment(txndata, thread, t2, start.Add(6*time.Second), "t2")
   235  	StartSegment(txndata, thread, start.Add(7*time.Second))
   236  	t3 := StartSegment(txndata, thread, start.Add(8*time.Second))
   237  	EndBasicSegment(txndata, thread, t3, start.Add(9*time.Second), "t3")
   238  	t4 := StartSegment(txndata, thread, start.Add(10*time.Second))
   239  	EndBasicSegment(txndata, thread, t4, start.Add(11*time.Second), "t4")
   240  
   241  	if thread.TotalTime() != 9*time.Second {
   242  		t.Error(thread.TotalTime())
   243  	}
   244  
   245  	metrics := newMetricTable(100, time.Now())
   246  	txndata.FinalName = "WebTransaction/Go/zip"
   247  	txndata.IsWeb = true
   248  	MergeBreakdownMetrics(txndata, metrics)
   249  	ExpectMetrics(t, metrics, []WantMetric{
   250  		{"Custom/t1", "", false, []float64{1, 1, 1, 1, 1, 1}},
   251  		{"Custom/t2", "", false, []float64{1, 1, 1, 1, 1, 1}},
   252  		{"Custom/t3", "", false, []float64{1, 1, 1, 1, 1, 1}},
   253  		{"Custom/t4", "", false, []float64{1, 1, 1, 1, 1, 1}},
   254  		{"Custom/t1", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   255  		{"Custom/t2", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   256  		{"Custom/t3", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   257  		{"Custom/t4", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   258  	})
   259  }
   260  
   261  func TestNilSpanEvent(t *testing.T) {
   262  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   263  
   264  	txndata := &TxnData{}
   265  	thread := &Thread{}
   266  	token := StartSegment(txndata, thread, start)
   267  	stop := start.Add(1 * time.Second)
   268  	end, err := endSegment(txndata, thread, token, stop)
   269  	if nil != err {
   270  		t.Error(err)
   271  	}
   272  
   273  	// A segment without a SpanId does not create a spanEvent.
   274  	if evt := end.spanEvent(); evt != nil {
   275  		t.Error(evt)
   276  	}
   277  }
   278  
   279  func TestDefaultSpanEvent(t *testing.T) {
   280  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   281  
   282  	txndata := &TxnData{}
   283  	thread := &Thread{}
   284  	token := StartSegment(txndata, thread, start)
   285  	stop := start.Add(1 * time.Second)
   286  	end, err := endSegment(txndata, thread, token, stop)
   287  	if nil != err {
   288  		t.Error(err)
   289  	}
   290  	end.SpanID = "123"
   291  	if evt := end.spanEvent(); evt != nil {
   292  		if evt.GUID != end.SpanID ||
   293  			evt.ParentID != end.ParentID ||
   294  			evt.Timestamp != end.start.Time ||
   295  			evt.Duration != end.duration ||
   296  			evt.IsEntrypoint {
   297  			t.Error(evt)
   298  		}
   299  	}
   300  }
   301  
   302  func TestGetRootSpanID(t *testing.T) {
   303  	txndata := &TxnData{
   304  		TraceIDGenerator: NewTraceIDGenerator(12345),
   305  	}
   306  	if id := txndata.getRootSpanID(); id != "d9466896a525ccbf" {
   307  		t.Error(id)
   308  	}
   309  	if id := txndata.getRootSpanID(); id != "d9466896a525ccbf" {
   310  		t.Error(id)
   311  	}
   312  }
   313  
   314  func TestCurrentSpanIdentifier(t *testing.T) {
   315  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   316  	txndata := &TxnData{
   317  		TraceIDGenerator: NewTraceIDGenerator(12345),
   318  	}
   319  	thread := &Thread{}
   320  	id := txndata.CurrentSpanIdentifier(thread)
   321  	if id != "d9466896a525ccbf" {
   322  		t.Error(id)
   323  	}
   324  
   325  	// After starting and ending a segment, the current span id is still the root.
   326  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   327  	_, err1 := endSegment(txndata, thread, t1, start.Add(3*time.Second))
   328  	if nil != err1 {
   329  		t.Error(err1)
   330  	}
   331  
   332  	id = txndata.CurrentSpanIdentifier(thread)
   333  	if id != "d9466896a525ccbf" {
   334  		t.Error(id)
   335  	}
   336  
   337  	// After starting a new segment, there should be a new current span id.
   338  	StartSegment(txndata, thread, start.Add(2*time.Second))
   339  	id2 := txndata.CurrentSpanIdentifier(thread)
   340  	if id2 != "bcfb32e050b264b8" {
   341  		t.Error(id2)
   342  	}
   343  }
   344  
   345  func TestDatastoreSpanAddress(t *testing.T) {
   346  	if s := datastoreSpanAddress("host", "portPathOrID"); s != "host:portPathOrID" {
   347  		t.Error(s)
   348  	}
   349  	if s := datastoreSpanAddress("host", ""); s != "host" {
   350  		t.Error(s)
   351  	}
   352  	if s := datastoreSpanAddress("", ""); s != "" {
   353  		t.Error(s)
   354  	}
   355  }
   356  
   357  func TestSegmentBasic(t *testing.T) {
   358  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   359  	txndata := &TxnData{}
   360  	thread := &Thread{}
   361  
   362  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   363  	t2 := StartSegment(txndata, thread, start.Add(2*time.Second))
   364  	EndBasicSegment(txndata, thread, t2, start.Add(3*time.Second), "t2")
   365  	EndBasicSegment(txndata, thread, t1, start.Add(4*time.Second), "t1")
   366  	t3 := StartSegment(txndata, thread, start.Add(5*time.Second))
   367  	t4 := StartSegment(txndata, thread, start.Add(6*time.Second))
   368  	EndBasicSegment(txndata, thread, t3, start.Add(7*time.Second), "t3")
   369  	EndBasicSegment(txndata, thread, t4, start.Add(8*time.Second), "out-of-order")
   370  	t5 := StartSegment(txndata, thread, start.Add(9*time.Second))
   371  	EndBasicSegment(txndata, thread, t5, start.Add(10*time.Second), "t1")
   372  
   373  	metrics := newMetricTable(100, time.Now())
   374  	txndata.FinalName = "WebTransaction/Go/zip"
   375  	txndata.IsWeb = true
   376  	MergeBreakdownMetrics(txndata, metrics)
   377  	ExpectMetrics(t, metrics, []WantMetric{
   378  		{"Custom/t1", "", false, []float64{2, 4, 3, 1, 3, 10}},
   379  		{"Custom/t2", "", false, []float64{1, 1, 1, 1, 1, 1}},
   380  		{"Custom/t3", "", false, []float64{1, 2, 2, 2, 2, 4}},
   381  		{"Custom/t1", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}},
   382  		{"Custom/t2", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   383  		{"Custom/t3", txndata.FinalName, false, []float64{1, 2, 2, 2, 2, 4}},
   384  	})
   385  }
   386  
   387  func parseURL(raw string) *url.URL {
   388  	u, _ := url.Parse(raw)
   389  	return u
   390  }
   391  
   392  func TestSegmentExternal(t *testing.T) {
   393  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   394  	txndata := &TxnData{}
   395  	thread := &Thread{}
   396  
   397  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   398  	t2 := StartSegment(txndata, thread, start.Add(2*time.Second))
   399  	EndExternalSegment(EndExternalParams{
   400  		TxnData: txndata,
   401  		Thread:  thread,
   402  		Start:   t2,
   403  		Now:     start.Add(3 * time.Second),
   404  		Logger:  logger.ShimLogger{},
   405  	})
   406  	EndExternalSegment(EndExternalParams{
   407  		TxnData: txndata,
   408  		Thread:  thread,
   409  		Start:   t1,
   410  		Now:     start.Add(4 * time.Second),
   411  		URL:     parseURL("http://f1.com"),
   412  		Host:    "f1",
   413  		Logger:  logger.ShimLogger{},
   414  	})
   415  	t3 := StartSegment(txndata, thread, start.Add(5*time.Second))
   416  	EndExternalSegment(EndExternalParams{
   417  		TxnData: txndata,
   418  		Thread:  thread,
   419  		Start:   t3,
   420  		Now:     start.Add(6 * time.Second),
   421  		URL:     parseURL("http://f1.com"),
   422  		Host:    "f1",
   423  		Logger:  logger.ShimLogger{},
   424  	})
   425  	t4 := StartSegment(txndata, thread, start.Add(7*time.Second))
   426  	t4.Stamp++
   427  	EndExternalSegment(EndExternalParams{
   428  		TxnData: txndata,
   429  		Thread:  thread,
   430  		Start:   t4,
   431  		Now:     start.Add(8 * time.Second),
   432  		URL:     parseURL("http://invalid-token.com"),
   433  		Host:    "invalid-token.com",
   434  		Logger:  logger.ShimLogger{},
   435  	})
   436  	if txndata.externalCallCount != 3 {
   437  		t.Error(txndata.externalCallCount)
   438  	}
   439  	if txndata.externalDuration != 5*time.Second {
   440  		t.Error(txndata.externalDuration)
   441  	}
   442  	metrics := newMetricTable(100, time.Now())
   443  	txndata.FinalName = "WebTransaction/Go/zip"
   444  	txndata.IsWeb = true
   445  	MergeBreakdownMetrics(txndata, metrics)
   446  	ExpectMetrics(t, metrics, []WantMetric{
   447  		{"External/all", "", true, []float64{3, 5, 4, 1, 3, 11}},
   448  		{"External/allWeb", "", true, []float64{3, 5, 4, 1, 3, 11}},
   449  		{"External/f1/all", "", false, []float64{2, 4, 3, 1, 3, 10}},
   450  		{"External/unknown/all", "", false, []float64{1, 1, 1, 1, 1, 1}},
   451  		{"External/f1/http", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}},
   452  		{"External/unknown/http", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   453  	})
   454  
   455  	metrics = newMetricTable(100, time.Now())
   456  	txndata.FinalName = "OtherTransaction/Go/zip"
   457  	txndata.IsWeb = false
   458  	MergeBreakdownMetrics(txndata, metrics)
   459  	ExpectMetrics(t, metrics, []WantMetric{
   460  		{"External/all", "", true, []float64{3, 5, 4, 1, 3, 11}},
   461  		{"External/allOther", "", true, []float64{3, 5, 4, 1, 3, 11}},
   462  		{"External/f1/all", "", false, []float64{2, 4, 3, 1, 3, 10}},
   463  		{"External/unknown/all", "", false, []float64{1, 1, 1, 1, 1, 1}},
   464  		{"External/f1/http", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}},
   465  		{"External/unknown/http", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   466  	})
   467  }
   468  
   469  func TestSegmentDatastore(t *testing.T) {
   470  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   471  	txndata := &TxnData{}
   472  	thread := &Thread{}
   473  
   474  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   475  	t2 := StartSegment(txndata, thread, start.Add(2*time.Second))
   476  	EndDatastoreSegment(EndDatastoreParams{
   477  		TxnData:    txndata,
   478  		Thread:     thread,
   479  		Start:      t2,
   480  		Now:        start.Add(3 * time.Second),
   481  		Product:    "MySQL",
   482  		Operation:  "SELECT",
   483  		Collection: "my_table",
   484  	})
   485  	EndDatastoreSegment(EndDatastoreParams{
   486  		TxnData:   txndata,
   487  		Thread:    thread,
   488  		Start:     t1,
   489  		Now:       start.Add(4 * time.Second),
   490  		Product:   "MySQL",
   491  		Operation: "SELECT",
   492  		// missing collection
   493  	})
   494  	t3 := StartSegment(txndata, thread, start.Add(5*time.Second))
   495  	EndDatastoreSegment(EndDatastoreParams{
   496  		TxnData:   txndata,
   497  		Thread:    thread,
   498  		Start:     t3,
   499  		Now:       start.Add(6 * time.Second),
   500  		Product:   "MySQL",
   501  		Operation: "SELECT",
   502  		// missing collection
   503  	})
   504  	t4 := StartSegment(txndata, thread, start.Add(7*time.Second))
   505  	t4.Stamp++
   506  	EndDatastoreSegment(EndDatastoreParams{
   507  		TxnData:   txndata,
   508  		Thread:    thread,
   509  		Start:     t4,
   510  		Now:       start.Add(8 * time.Second),
   511  		Product:   "MySQL",
   512  		Operation: "invalid-token",
   513  	})
   514  	t5 := StartSegment(txndata, thread, start.Add(9*time.Second))
   515  	EndDatastoreSegment(EndDatastoreParams{
   516  		TxnData: txndata,
   517  		Thread:  thread,
   518  		Start:   t5,
   519  		Now:     start.Add(10 * time.Second),
   520  		// missing datastore, collection, and operation
   521  	})
   522  
   523  	if txndata.datastoreCallCount != 4 {
   524  		t.Error(txndata.datastoreCallCount)
   525  	}
   526  	if txndata.datastoreDuration != 6*time.Second {
   527  		t.Error(txndata.datastoreDuration)
   528  	}
   529  	metrics := newMetricTable(100, time.Now())
   530  	txndata.FinalName = "WebTransaction/Go/zip"
   531  	txndata.IsWeb = true
   532  	MergeBreakdownMetrics(txndata, metrics)
   533  	ExpectMetrics(t, metrics, []WantMetric{
   534  		{"Datastore/all", "", true, []float64{4, 6, 5, 1, 3, 12}},
   535  		{"Datastore/allWeb", "", true, []float64{4, 6, 5, 1, 3, 12}},
   536  		{"Datastore/MySQL/all", "", true, []float64{3, 5, 4, 1, 3, 11}},
   537  		{"Datastore/MySQL/allWeb", "", true, []float64{3, 5, 4, 1, 3, 11}},
   538  		{"Datastore/Unknown/all", "", true, []float64{1, 1, 1, 1, 1, 1}},
   539  		{"Datastore/Unknown/allWeb", "", true, []float64{1, 1, 1, 1, 1, 1}},
   540  		{"Datastore/operation/MySQL/SELECT", "", false, []float64{3, 5, 4, 1, 3, 11}},
   541  		{"Datastore/operation/MySQL/SELECT", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}},
   542  		{"Datastore/operation/Unknown/other", "", false, []float64{1, 1, 1, 1, 1, 1}},
   543  		{"Datastore/operation/Unknown/other", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   544  		{"Datastore/statement/MySQL/my_table/SELECT", "", false, []float64{1, 1, 1, 1, 1, 1}},
   545  		{"Datastore/statement/MySQL/my_table/SELECT", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   546  	})
   547  
   548  	metrics = newMetricTable(100, time.Now())
   549  	txndata.FinalName = "OtherTransaction/Go/zip"
   550  	txndata.IsWeb = false
   551  	MergeBreakdownMetrics(txndata, metrics)
   552  	ExpectMetrics(t, metrics, []WantMetric{
   553  		{"Datastore/all", "", true, []float64{4, 6, 5, 1, 3, 12}},
   554  		{"Datastore/allOther", "", true, []float64{4, 6, 5, 1, 3, 12}},
   555  		{"Datastore/MySQL/all", "", true, []float64{3, 5, 4, 1, 3, 11}},
   556  		{"Datastore/MySQL/allOther", "", true, []float64{3, 5, 4, 1, 3, 11}},
   557  		{"Datastore/Unknown/all", "", true, []float64{1, 1, 1, 1, 1, 1}},
   558  		{"Datastore/Unknown/allOther", "", true, []float64{1, 1, 1, 1, 1, 1}},
   559  		{"Datastore/operation/MySQL/SELECT", "", false, []float64{3, 5, 4, 1, 3, 11}},
   560  		{"Datastore/operation/MySQL/SELECT", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}},
   561  		{"Datastore/operation/Unknown/other", "", false, []float64{1, 1, 1, 1, 1, 1}},
   562  		{"Datastore/operation/Unknown/other", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   563  		{"Datastore/statement/MySQL/my_table/SELECT", "", false, []float64{1, 1, 1, 1, 1, 1}},
   564  		{"Datastore/statement/MySQL/my_table/SELECT", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}},
   565  	})
   566  }
   567  
   568  func TestDatastoreInstancesCrossAgent(t *testing.T) {
   569  	var testcases []struct {
   570  		Name           string `json:"name"`
   571  		SystemHostname string `json:"system_hostname"`
   572  		DBHostname     string `json:"db_hostname"`
   573  		Product        string `json:"product"`
   574  		Port           int    `json:"port"`
   575  		Socket         string `json:"unix_socket"`
   576  		DatabasePath   string `json:"database_path"`
   577  		ExpectedMetric string `json:"expected_instance_metric"`
   578  	}
   579  
   580  	err := crossagent.ReadJSON("datastores/datastore_instances.json", &testcases)
   581  	if err != nil {
   582  		t.Fatal(err)
   583  	}
   584  
   585  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   586  
   587  	for _, tc := range testcases {
   588  		portPathOrID := ""
   589  		if 0 != tc.Port {
   590  			portPathOrID = strconv.Itoa(tc.Port)
   591  		} else if "" != tc.Socket {
   592  			portPathOrID = tc.Socket
   593  		} else if "" != tc.DatabasePath {
   594  			portPathOrID = tc.DatabasePath
   595  			// These tests makes weird assumptions.
   596  			tc.DBHostname = "localhost"
   597  		}
   598  
   599  		txndata := &TxnData{}
   600  		thread := &Thread{}
   601  
   602  		s := StartSegment(txndata, thread, start)
   603  		EndDatastoreSegment(EndDatastoreParams{
   604  			Thread:       thread,
   605  			TxnData:      txndata,
   606  			Start:        s,
   607  			Now:          start.Add(1 * time.Second),
   608  			Product:      tc.Product,
   609  			Operation:    "SELECT",
   610  			Collection:   "my_table",
   611  			PortPathOrID: portPathOrID,
   612  			Host:         tc.DBHostname,
   613  		})
   614  
   615  		expect := strings.Replace(tc.ExpectedMetric,
   616  			tc.SystemHostname, ThisHost, -1)
   617  
   618  		metrics := newMetricTable(100, time.Now())
   619  		txndata.FinalName = "OtherTransaction/Go/zip"
   620  		txndata.IsWeb = false
   621  		MergeBreakdownMetrics(txndata, metrics)
   622  		data := []float64{1, 1, 1, 1, 1, 1}
   623  		ExpectMetrics(ExtendValidator(t, tc.Name), metrics, []WantMetric{
   624  			{"Datastore/all", "", true, data},
   625  			{"Datastore/allOther", "", true, data},
   626  			{"Datastore/" + tc.Product + "/all", "", true, data},
   627  			{"Datastore/" + tc.Product + "/allOther", "", true, data},
   628  			{"Datastore/operation/" + tc.Product + "/SELECT", "", false, data},
   629  			{"Datastore/statement/" + tc.Product + "/my_table/SELECT", "", false, data},
   630  			{"Datastore/statement/" + tc.Product + "/my_table/SELECT", txndata.FinalName, false, data},
   631  			{expect, "", false, data},
   632  		})
   633  	}
   634  }
   635  
   636  func TestGenericSpanEventCreation(t *testing.T) {
   637  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   638  	txndata := &TxnData{
   639  		TraceIDGenerator: NewTraceIDGenerator(12345),
   640  	}
   641  	thread := &Thread{}
   642  
   643  	// Enable that which is necessary to generate span events when segments are ended.
   644  	txndata.LazilyCalculateSampled = func() bool { return true }
   645  	txndata.SpanEventsEnabled = true
   646  
   647  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   648  	EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1")
   649  
   650  	// Since a basic segment has just ended, there should be exactly one generic span event in txndata.spanEvents[]
   651  	if 1 != len(txndata.spanEvents) {
   652  		t.Error(txndata.spanEvents)
   653  	}
   654  	if txndata.spanEvents[0].Category != spanCategoryGeneric {
   655  		t.Error(txndata.spanEvents[0].Category)
   656  	}
   657  }
   658  
   659  func TestSpanEventNotSampled(t *testing.T) {
   660  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   661  	txndata := &TxnData{
   662  		TraceIDGenerator: NewTraceIDGenerator(12345),
   663  	}
   664  	thread := &Thread{}
   665  
   666  	txndata.LazilyCalculateSampled = func() bool { return false }
   667  	txndata.SpanEventsEnabled = true
   668  
   669  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   670  	EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1")
   671  
   672  	if 0 != len(txndata.spanEvents) {
   673  		t.Error(txndata.spanEvents)
   674  	}
   675  }
   676  
   677  func TestSpanEventNotEnabled(t *testing.T) {
   678  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   679  	txndata := &TxnData{
   680  		TraceIDGenerator: NewTraceIDGenerator(12345),
   681  	}
   682  	thread := &Thread{}
   683  
   684  	txndata.LazilyCalculateSampled = func() bool { return true }
   685  	txndata.SpanEventsEnabled = false
   686  
   687  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   688  	EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1")
   689  
   690  	if 0 != len(txndata.spanEvents) {
   691  		t.Error(txndata.spanEvents)
   692  	}
   693  }
   694  
   695  func TestDatastoreSpanEventCreation(t *testing.T) {
   696  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   697  	txndata := &TxnData{
   698  		TraceIDGenerator: NewTraceIDGenerator(12345),
   699  	}
   700  	thread := &Thread{}
   701  
   702  	// Enable that which is necessary to generate span events when segments are ended.
   703  	txndata.LazilyCalculateSampled = func() bool { return true }
   704  	txndata.SpanEventsEnabled = true
   705  
   706  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   707  	EndDatastoreSegment(EndDatastoreParams{
   708  		TxnData:    txndata,
   709  		Thread:     thread,
   710  		Start:      t1,
   711  		Now:        start.Add(3 * time.Second),
   712  		Product:    "MySQL",
   713  		Operation:  "SELECT",
   714  		Collection: "my_table",
   715  	})
   716  
   717  	// Since a datastore segment has just ended, there should be exactly one datastore span event in txndata.spanEvents[]
   718  	if 1 != len(txndata.spanEvents) {
   719  		t.Error(txndata.spanEvents)
   720  	}
   721  	if txndata.spanEvents[0].Category != spanCategoryDatastore {
   722  		t.Error(txndata.spanEvents[0].Category)
   723  	}
   724  }
   725  
   726  func TestHTTPSpanEventCreation(t *testing.T) {
   727  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   728  	txndata := &TxnData{
   729  		TraceIDGenerator: NewTraceIDGenerator(12345),
   730  	}
   731  	thread := &Thread{}
   732  
   733  	// Enable that which is necessary to generate span events when segments are ended.
   734  	txndata.LazilyCalculateSampled = func() bool { return true }
   735  	txndata.SpanEventsEnabled = true
   736  
   737  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   738  	EndExternalSegment(EndExternalParams{
   739  		TxnData: txndata,
   740  		Thread:  thread,
   741  		Start:   t1,
   742  		Now:     start.Add(3 * time.Second),
   743  		URL:     nil,
   744  		Logger:  logger.ShimLogger{},
   745  	})
   746  
   747  	// Since an external segment has just ended, there should be exactly one HTTP span event in txndata.spanEvents[]
   748  	if 1 != len(txndata.spanEvents) {
   749  		t.Error(txndata.spanEvents)
   750  	}
   751  	if txndata.spanEvents[0].Category != spanCategoryHTTP {
   752  		t.Error(txndata.spanEvents[0].Category)
   753  	}
   754  }
   755  
   756  func TestExternalSegmentCAT(t *testing.T) {
   757  	// Test that when the reading the response CAT headers fails, an external
   758  	// segment is still created.
   759  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   760  	txndata := &TxnData{
   761  		TraceIDGenerator: NewTraceIDGenerator(12345),
   762  	}
   763  	txndata.CrossProcess.Enabled = true
   764  	thread := &Thread{}
   765  
   766  	resp := &http.Response{Header: http.Header{}}
   767  	resp.Header.Add(cat.NewRelicAppDataName, "bad header value")
   768  
   769  	t1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   770  	err := EndExternalSegment(EndExternalParams{
   771  		TxnData: txndata,
   772  		Thread:  thread,
   773  		Start:   t1,
   774  		Now:     start.Add(4 * time.Second),
   775  		URL:     parseURL("http://f1.com"),
   776  		Logger:  logger.ShimLogger{},
   777  	})
   778  
   779  	if nil != err {
   780  		t.Error("EndExternalSegment returned an err:", err)
   781  	}
   782  	if txndata.externalCallCount != 1 {
   783  		t.Error(txndata.externalCallCount)
   784  	}
   785  	if txndata.externalDuration != 3*time.Second {
   786  		t.Error(txndata.externalDuration)
   787  	}
   788  
   789  	metrics := newMetricTable(100, time.Now())
   790  	txndata.FinalName = "OtherTransaction/Go/zip"
   791  	txndata.IsWeb = false
   792  	MergeBreakdownMetrics(txndata, metrics)
   793  	ExpectMetrics(t, metrics, []WantMetric{
   794  		{"External/all", "", true, []float64{1, 3, 3, 3, 3, 9}},
   795  		{"External/allOther", "", true, []float64{1, 3, 3, 3, 3, 9}},
   796  		{"External/f1.com/all", "", false, []float64{1, 3, 3, 3, 3, 9}},
   797  		{"External/f1.com/http", txndata.FinalName, false, []float64{1, 3, 3, 3, 3, 9}},
   798  	})
   799  }
   800  
   801  func TestEndMessageSegment(t *testing.T) {
   802  	start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)
   803  	txndata := &TxnData{
   804  		TraceIDGenerator: NewTraceIDGenerator(12345),
   805  	}
   806  	txndata.CrossProcess.Enabled = true
   807  	txndata.LazilyCalculateSampled = func() bool { return true }
   808  	txndata.SpanEventsEnabled = true
   809  	thread := &Thread{}
   810  
   811  	seg1 := StartSegment(txndata, thread, start.Add(1*time.Second))
   812  	seg2 := StartSegment(txndata, thread, start.Add(2*time.Second))
   813  	EndMessageSegment(EndMessageParams{
   814  		TxnData:         txndata,
   815  		Thread:          thread,
   816  		Start:           seg1,
   817  		Now:             start.Add(3 * time.Second),
   818  		Logger:          nil,
   819  		DestinationName: "MyTopic",
   820  		Library:         "Kafka",
   821  		DestinationType: "Topic",
   822  	})
   823  	EndMessageSegment(EndMessageParams{
   824  		TxnData:         txndata,
   825  		Thread:          thread,
   826  		Start:           seg2,
   827  		Now:             start.Add(4 * time.Second),
   828  		Logger:          nil,
   829  		DestinationName: "MyOtherTopic",
   830  		Library:         "Kafka",
   831  		DestinationType: "Topic",
   832  	})
   833  
   834  	metrics := newMetricTable(100, time.Now())
   835  	txndata.FinalName = "WebTransaction/Go/zip"
   836  	txndata.IsWeb = true
   837  	MergeBreakdownMetrics(txndata, metrics)
   838  	ExpectMetrics(t, metrics, []WantMetric{
   839  		{"MessageBroker/Kafka/Topic/Produce/Named/MyTopic", "WebTransaction/Go/zip", false, []float64{1, 2, 2, 2, 2, 4}},
   840  		{"MessageBroker/Kafka/Topic/Produce/Named/MyTopic", "", false, []float64{1, 2, 2, 2, 2, 4}},
   841  	})
   842  }