github.com/newrelic/go-agent@v3.26.0+incompatible/internal/tracing.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  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"net/url"
    12  	"time"
    13  
    14  	"github.com/newrelic/go-agent/internal/cat"
    15  	"github.com/newrelic/go-agent/internal/jsonx"
    16  	"github.com/newrelic/go-agent/internal/logger"
    17  	"github.com/newrelic/go-agent/internal/sysinfo"
    18  )
    19  
    20  // MarshalJSON limits the number of decimals.
    21  func (p *Priority) MarshalJSON() ([]byte, error) {
    22  	return []byte(fmt.Sprintf(priorityFormat, *p)), nil
    23  }
    24  
    25  // WriteJSON limits the number of decimals.
    26  func (p Priority) WriteJSON(buf *bytes.Buffer) {
    27  	fmt.Fprintf(buf, priorityFormat, p)
    28  }
    29  
    30  // TxnEvent represents a transaction.
    31  // https://source.datanerd.us/agents/agent-specs/blob/master/Transaction-Events-PORTED.md
    32  // https://newrelic.atlassian.net/wiki/display/eng/Agent+Support+for+Synthetics%3A+Forced+Transaction+Traces+and+Analytic+Events
    33  type TxnEvent struct {
    34  	FinalName string
    35  	Start     time.Time
    36  	Duration  time.Duration
    37  	TotalTime time.Duration
    38  	Queuing   time.Duration
    39  	Zone      ApdexZone
    40  	Attrs     *Attributes
    41  	DatastoreExternalTotals
    42  	CrossProcess TxnCrossProcess
    43  	BetterCAT    BetterCAT
    44  	HasError     bool
    45  }
    46  
    47  // BetterCAT stores the transaction's priority and all fields related
    48  // to a DistributedTracer's Cross-Application Trace.
    49  type BetterCAT struct {
    50  	Enabled  bool
    51  	Priority Priority
    52  	Sampled  bool
    53  	Inbound  *Payload
    54  	ID       string
    55  }
    56  
    57  // TraceID returns the trace id.
    58  func (e BetterCAT) TraceID() string {
    59  	if nil != e.Inbound {
    60  		return e.Inbound.TracedID
    61  	}
    62  	return e.ID
    63  }
    64  
    65  // TxnData contains the recorded data of a transaction.
    66  type TxnData struct {
    67  	TxnEvent
    68  	IsWeb          bool
    69  	Name           string    // Work in progress name.
    70  	Errors         TxnErrors // Lazily initialized.
    71  	Stop           time.Time
    72  	ApdexThreshold time.Duration
    73  
    74  	stamp           segmentStamp
    75  	threadIDCounter uint64
    76  
    77  	TraceIDGenerator       *TraceIDGenerator
    78  	LazilyCalculateSampled func() bool
    79  	SpanEventsEnabled      bool
    80  	rootSpanID             string
    81  	spanEvents             []*SpanEvent
    82  
    83  	customSegments    map[string]*metricData
    84  	datastoreSegments map[DatastoreMetricKey]*metricData
    85  	externalSegments  map[externalMetricKey]*metricData
    86  	messageSegments   map[MessageMetricKey]*metricData
    87  
    88  	TxnTrace
    89  
    90  	SlowQueriesEnabled bool
    91  	SlowQueryThreshold time.Duration
    92  	SlowQueries        *slowQueries
    93  
    94  	// These better CAT supportability fields are left outside of
    95  	// TxnEvent.BetterCAT to minimize the size of transaction event memory.
    96  	DistributedTracingSupport
    97  }
    98  
    99  func (t *TxnData) saveTraceSegment(end segmentEnd, name string, attrs spanAttributeMap, externalGUID string) {
   100  	attrs = t.Attrs.filterSpanAttributes(attrs, destSegment)
   101  	t.TxnTrace.witnessNode(end, name, attrs, externalGUID)
   102  }
   103  
   104  // Thread contains a segment stack that is used to track segment parenting time
   105  // within a single goroutine.
   106  type Thread struct {
   107  	threadID uint64
   108  	stack    []segmentFrame
   109  	// start and end are used to track the TotalTime this Thread was active.
   110  	start time.Time
   111  	end   time.Time
   112  }
   113  
   114  // RecordActivity indicates that activity happened at this time on this
   115  // goroutine which helps track total time.
   116  func (thread *Thread) RecordActivity(now time.Time) {
   117  	if thread.start.IsZero() || now.Before(thread.start) {
   118  		thread.start = now
   119  	}
   120  	if now.After(thread.end) {
   121  		thread.end = now
   122  	}
   123  }
   124  
   125  // TotalTime returns the amount to time that this thread contributes to the
   126  // total time.
   127  func (thread *Thread) TotalTime() time.Duration {
   128  	if thread.start.Before(thread.end) {
   129  		return thread.end.Sub(thread.start)
   130  	}
   131  	return 0
   132  }
   133  
   134  // NewThread returns a new Thread to track segments in a new goroutine.
   135  func NewThread(txndata *TxnData) *Thread {
   136  	// Each thread needs a unique ID.
   137  	txndata.threadIDCounter++
   138  	return &Thread{
   139  		threadID: txndata.threadIDCounter,
   140  	}
   141  }
   142  
   143  type segmentStamp uint64
   144  
   145  type segmentTime struct {
   146  	Stamp segmentStamp
   147  	Time  time.Time
   148  }
   149  
   150  // SegmentStartTime is embedded into the top level segments (rather than
   151  // segmentTime) to minimize the structure sizes to minimize allocations.
   152  type SegmentStartTime struct {
   153  	Stamp segmentStamp
   154  	Depth int
   155  }
   156  
   157  type stringJSONWriter string
   158  
   159  func (s stringJSONWriter) WriteJSON(buf *bytes.Buffer) {
   160  	jsonx.AppendString(buf, string(s))
   161  }
   162  
   163  // spanAttributeMap is used for span attributes and segment attributes. The
   164  // value is a jsonWriter to allow for segment query parameters.
   165  type spanAttributeMap map[SpanAttribute]jsonWriter
   166  
   167  func (m *spanAttributeMap) addString(key SpanAttribute, val string) {
   168  	if "" != val {
   169  		m.add(key, stringJSONWriter(val))
   170  	}
   171  }
   172  
   173  func (m *spanAttributeMap) add(key SpanAttribute, val jsonWriter) {
   174  	if *m == nil {
   175  		*m = make(spanAttributeMap)
   176  	}
   177  	(*m)[key] = val
   178  }
   179  
   180  func (m spanAttributeMap) copy() spanAttributeMap {
   181  	if len(m) == 0 {
   182  		return nil
   183  	}
   184  	cpy := make(spanAttributeMap, len(m))
   185  	for k, v := range m {
   186  		cpy[k] = v
   187  	}
   188  	return cpy
   189  }
   190  
   191  type segmentFrame struct {
   192  	segmentTime
   193  	children   time.Duration
   194  	spanID     string
   195  	attributes spanAttributeMap
   196  }
   197  
   198  type segmentEnd struct {
   199  	start      segmentTime
   200  	stop       segmentTime
   201  	duration   time.Duration
   202  	exclusive  time.Duration
   203  	SpanID     string
   204  	ParentID   string
   205  	threadID   uint64
   206  	attributes spanAttributeMap
   207  }
   208  
   209  func (end segmentEnd) spanEvent() *SpanEvent {
   210  	if "" == end.SpanID {
   211  		return nil
   212  	}
   213  	return &SpanEvent{
   214  		GUID:         end.SpanID,
   215  		ParentID:     end.ParentID,
   216  		Timestamp:    end.start.Time,
   217  		Duration:     end.duration,
   218  		Attributes:   end.attributes,
   219  		IsEntrypoint: false,
   220  	}
   221  }
   222  
   223  const (
   224  	datastoreProductUnknown   = "Unknown"
   225  	datastoreOperationUnknown = "other"
   226  )
   227  
   228  // HasErrors indicates whether the transaction had errors.
   229  func (t *TxnData) HasErrors() bool {
   230  	return len(t.Errors) > 0
   231  }
   232  
   233  func (t *TxnData) time(now time.Time) segmentTime {
   234  	// Update the stamp before using it so that a 0 stamp can be special.
   235  	t.stamp++
   236  	return segmentTime{
   237  		Time:  now,
   238  		Stamp: t.stamp,
   239  	}
   240  }
   241  
   242  // AddAgentSpanAttribute allows attributes to be added to spans.
   243  func (thread *Thread) AddAgentSpanAttribute(key SpanAttribute, val string) {
   244  	if len(thread.stack) > 0 {
   245  		thread.stack[len(thread.stack)-1].attributes.addString(key, val)
   246  	}
   247  }
   248  
   249  // StartSegment begins a segment.
   250  func StartSegment(t *TxnData, thread *Thread, now time.Time) SegmentStartTime {
   251  	tm := t.time(now)
   252  	thread.stack = append(thread.stack, segmentFrame{
   253  		segmentTime: tm,
   254  		children:    0,
   255  	})
   256  
   257  	return SegmentStartTime{
   258  		Stamp: tm.Stamp,
   259  		Depth: len(thread.stack) - 1,
   260  	}
   261  }
   262  
   263  func (t *TxnData) getRootSpanID() string {
   264  	if "" == t.rootSpanID {
   265  		t.rootSpanID = t.TraceIDGenerator.GenerateTraceID()
   266  	}
   267  	return t.rootSpanID
   268  }
   269  
   270  // CurrentSpanIdentifier returns the identifier of the span at the top of the
   271  // segment stack.
   272  func (t *TxnData) CurrentSpanIdentifier(thread *Thread) string {
   273  	if 0 == len(thread.stack) {
   274  		return t.getRootSpanID()
   275  	}
   276  	if "" == thread.stack[len(thread.stack)-1].spanID {
   277  		thread.stack[len(thread.stack)-1].spanID = t.TraceIDGenerator.GenerateTraceID()
   278  	}
   279  	return thread.stack[len(thread.stack)-1].spanID
   280  }
   281  
   282  func (t *TxnData) saveSpanEvent(e *SpanEvent) {
   283  	e.Attributes = t.Attrs.filterSpanAttributes(e.Attributes, destSpan)
   284  	if len(t.spanEvents) < MaxSpanEvents {
   285  		t.spanEvents = append(t.spanEvents, e)
   286  	}
   287  }
   288  
   289  var (
   290  	errMalformedSegment = errors.New("segment identifier malformed: perhaps unsafe code has modified it?")
   291  	errSegmentOrder     = errors.New(`improper segment use: the Transaction must be used ` +
   292  		`in a single goroutine and segments must be ended in "last started first ended" order: ` +
   293  		`see https://github.com/newrelic/go-agent/blob/master/GUIDE.md#segments`)
   294  )
   295  
   296  func endSegment(t *TxnData, thread *Thread, start SegmentStartTime, now time.Time) (segmentEnd, error) {
   297  	if 0 == start.Stamp {
   298  		return segmentEnd{}, errMalformedSegment
   299  	}
   300  	if start.Depth >= len(thread.stack) {
   301  		return segmentEnd{}, errSegmentOrder
   302  	}
   303  	if start.Depth < 0 {
   304  		return segmentEnd{}, errMalformedSegment
   305  	}
   306  	frame := thread.stack[start.Depth]
   307  	if start.Stamp != frame.Stamp {
   308  		return segmentEnd{}, errSegmentOrder
   309  	}
   310  
   311  	var children time.Duration
   312  	for i := start.Depth; i < len(thread.stack); i++ {
   313  		children += thread.stack[i].children
   314  	}
   315  	s := segmentEnd{
   316  		stop:       t.time(now),
   317  		start:      frame.segmentTime,
   318  		attributes: frame.attributes,
   319  	}
   320  	if s.stop.Time.After(s.start.Time) {
   321  		s.duration = s.stop.Time.Sub(s.start.Time)
   322  	}
   323  	if s.duration > children {
   324  		s.exclusive = s.duration - children
   325  	}
   326  
   327  	// Note that we expect (depth == (len(t.stack) - 1)).  However, if
   328  	// (depth < (len(t.stack) - 1)), that's ok: could be a panic popped
   329  	// some stack frames (and the consumer was not using defer).
   330  
   331  	if start.Depth > 0 {
   332  		thread.stack[start.Depth-1].children += s.duration
   333  	}
   334  
   335  	thread.stack = thread.stack[0:start.Depth]
   336  
   337  	if t.SpanEventsEnabled && t.LazilyCalculateSampled() {
   338  		s.SpanID = frame.spanID
   339  		if "" == s.SpanID {
   340  			s.SpanID = t.TraceIDGenerator.GenerateTraceID()
   341  		}
   342  		// Note that the current span identifier is the parent's
   343  		// identifier because we've already popped the segment that's
   344  		// ending off of the stack.
   345  		s.ParentID = t.CurrentSpanIdentifier(thread)
   346  	}
   347  
   348  	s.threadID = thread.threadID
   349  
   350  	thread.RecordActivity(s.start.Time)
   351  	thread.RecordActivity(s.stop.Time)
   352  
   353  	return s, nil
   354  }
   355  
   356  // EndBasicSegment ends a basic segment.
   357  func EndBasicSegment(t *TxnData, thread *Thread, start SegmentStartTime, now time.Time, name string) error {
   358  	end, err := endSegment(t, thread, start, now)
   359  	if nil != err {
   360  		return err
   361  	}
   362  	if nil == t.customSegments {
   363  		t.customSegments = make(map[string]*metricData)
   364  	}
   365  	m := metricDataFromDuration(end.duration, end.exclusive)
   366  	if data, ok := t.customSegments[name]; ok {
   367  		data.aggregate(m)
   368  	} else {
   369  		// Use `new` in place of &m so that m is not
   370  		// automatically moved to the heap.
   371  		cpy := new(metricData)
   372  		*cpy = m
   373  		t.customSegments[name] = cpy
   374  	}
   375  
   376  	if t.TxnTrace.considerNode(end) {
   377  		attributes := end.attributes.copy()
   378  		t.saveTraceSegment(end, customSegmentMetric(name), attributes, "")
   379  	}
   380  
   381  	if evt := end.spanEvent(); evt != nil {
   382  		evt.Name = customSegmentMetric(name)
   383  		evt.Category = spanCategoryGeneric
   384  		t.saveSpanEvent(evt)
   385  	}
   386  
   387  	return nil
   388  }
   389  
   390  // EndExternalParams contains the parameters for EndExternalSegment.
   391  type EndExternalParams struct {
   392  	TxnData  *TxnData
   393  	Thread   *Thread
   394  	Start    SegmentStartTime
   395  	Now      time.Time
   396  	Logger   logger.Logger
   397  	Response *http.Response
   398  	URL      *url.URL
   399  	Host     string
   400  	Library  string
   401  	Method   string
   402  }
   403  
   404  // EndExternalSegment ends an external segment.
   405  func EndExternalSegment(p EndExternalParams) error {
   406  	t := p.TxnData
   407  	end, err := endSegment(t, p.Thread, p.Start, p.Now)
   408  	if nil != err {
   409  		return err
   410  	}
   411  
   412  	// Use the Host field if present, otherwise use host in the URL.
   413  	if p.Host == "" && p.URL != nil {
   414  		p.Host = p.URL.Host
   415  	}
   416  	if p.Host == "" {
   417  		p.Host = "unknown"
   418  	}
   419  	if p.Library == "" {
   420  		p.Library = "http"
   421  	}
   422  
   423  	var appData *cat.AppDataHeader
   424  	if p.Response != nil {
   425  		hdr := HTTPHeaderToAppData(p.Response.Header)
   426  		appData, err = t.CrossProcess.ParseAppData(hdr)
   427  		if err != nil {
   428  			if p.Logger.DebugEnabled() {
   429  				p.Logger.Debug("failure to parse cross application response header", map[string]interface{}{
   430  					"err":    err.Error(),
   431  					"header": hdr,
   432  				})
   433  			}
   434  		}
   435  	}
   436  
   437  	var crossProcessID string
   438  	var transactionName string
   439  	var transactionGUID string
   440  	if appData != nil {
   441  		crossProcessID = appData.CrossProcessID
   442  		transactionName = appData.TransactionName
   443  		transactionGUID = appData.TransactionGUID
   444  	}
   445  
   446  	key := externalMetricKey{
   447  		Host:                    p.Host,
   448  		Library:                 p.Library,
   449  		Method:                  p.Method,
   450  		ExternalCrossProcessID:  crossProcessID,
   451  		ExternalTransactionName: transactionName,
   452  	}
   453  	if nil == t.externalSegments {
   454  		t.externalSegments = make(map[externalMetricKey]*metricData)
   455  	}
   456  	t.externalCallCount++
   457  	t.externalDuration += end.duration
   458  	m := metricDataFromDuration(end.duration, end.exclusive)
   459  	if data, ok := t.externalSegments[key]; ok {
   460  		data.aggregate(m)
   461  	} else {
   462  		// Use `new` in place of &m so that m is not
   463  		// automatically moved to the heap.
   464  		cpy := new(metricData)
   465  		*cpy = m
   466  		t.externalSegments[key] = cpy
   467  	}
   468  
   469  	if t.TxnTrace.considerNode(end) {
   470  		attributes := end.attributes.copy()
   471  		if p.Library == "http" {
   472  			attributes.addString(spanAttributeHTTPURL, SafeURL(p.URL))
   473  		}
   474  		t.saveTraceSegment(end, key.scopedMetric(), attributes, transactionGUID)
   475  	}
   476  
   477  	if evt := end.spanEvent(); evt != nil {
   478  		evt.Name = key.scopedMetric()
   479  		evt.Category = spanCategoryHTTP
   480  		evt.Kind = "client"
   481  		evt.Component = p.Library
   482  		if p.Library == "http" {
   483  			evt.Attributes.addString(spanAttributeHTTPURL, SafeURL(p.URL))
   484  			evt.Attributes.addString(spanAttributeHTTPMethod, p.Method)
   485  		}
   486  		t.saveSpanEvent(evt)
   487  	}
   488  
   489  	return nil
   490  }
   491  
   492  // EndMessageParams contains the parameters for EndMessageSegment.
   493  type EndMessageParams struct {
   494  	TxnData         *TxnData
   495  	Thread          *Thread
   496  	Start           SegmentStartTime
   497  	Now             time.Time
   498  	Logger          logger.Logger
   499  	DestinationName string
   500  	Library         string
   501  	DestinationType string
   502  	DestinationTemp bool
   503  }
   504  
   505  // EndMessageSegment ends an external segment.
   506  func EndMessageSegment(p EndMessageParams) error {
   507  	t := p.TxnData
   508  	end, err := endSegment(t, p.Thread, p.Start, p.Now)
   509  	if nil != err {
   510  		return err
   511  	}
   512  
   513  	key := MessageMetricKey{
   514  		Library:         p.Library,
   515  		DestinationType: p.DestinationType,
   516  		DestinationName: p.DestinationName,
   517  		DestinationTemp: p.DestinationTemp,
   518  	}
   519  
   520  	if nil == t.messageSegments {
   521  		t.messageSegments = make(map[MessageMetricKey]*metricData)
   522  	}
   523  	m := metricDataFromDuration(end.duration, end.exclusive)
   524  	if data, ok := t.messageSegments[key]; ok {
   525  		data.aggregate(m)
   526  	} else {
   527  		// Use `new` in place of &m so that m is not
   528  		// automatically moved to the heap.
   529  		cpy := new(metricData)
   530  		*cpy = m
   531  		t.messageSegments[key] = cpy
   532  	}
   533  
   534  	if t.TxnTrace.considerNode(end) {
   535  		attributes := end.attributes.copy()
   536  		t.saveTraceSegment(end, key.Name(), attributes, "")
   537  	}
   538  
   539  	if evt := end.spanEvent(); evt != nil {
   540  		evt.Name = key.Name()
   541  		evt.Category = spanCategoryGeneric
   542  		t.saveSpanEvent(evt)
   543  	}
   544  
   545  	return nil
   546  }
   547  
   548  // EndDatastoreParams contains the parameters for EndDatastoreSegment.
   549  type EndDatastoreParams struct {
   550  	TxnData            *TxnData
   551  	Thread             *Thread
   552  	Start              SegmentStartTime
   553  	Now                time.Time
   554  	Product            string
   555  	Collection         string
   556  	Operation          string
   557  	ParameterizedQuery string
   558  	QueryParameters    map[string]interface{}
   559  	Host               string
   560  	PortPathOrID       string
   561  	Database           string
   562  }
   563  
   564  const (
   565  	unknownDatastoreHost         = "unknown"
   566  	unknownDatastorePortPathOrID = "unknown"
   567  )
   568  
   569  var (
   570  	// ThisHost is the system hostname.
   571  	ThisHost = func() string {
   572  		if h, err := sysinfo.Hostname(); nil == err {
   573  			return h
   574  		}
   575  		return unknownDatastoreHost
   576  	}()
   577  	hostsToReplace = map[string]struct{}{
   578  		"localhost":       {},
   579  		"127.0.0.1":       {},
   580  		"0.0.0.0":         {},
   581  		"0:0:0:0:0:0:0:1": {},
   582  		"::1":             {},
   583  		"0:0:0:0:0:0:0:0": {},
   584  		"::":              {},
   585  	}
   586  )
   587  
   588  func (t TxnData) slowQueryWorthy(d time.Duration) bool {
   589  	return t.SlowQueriesEnabled && (d >= t.SlowQueryThreshold)
   590  }
   591  
   592  func datastoreSpanAddress(host, portPathOrID string) string {
   593  	if "" != host && "" != portPathOrID {
   594  		return host + ":" + portPathOrID
   595  	}
   596  	if "" != host {
   597  		return host
   598  	}
   599  	return portPathOrID
   600  }
   601  
   602  // EndDatastoreSegment ends a datastore segment.
   603  func EndDatastoreSegment(p EndDatastoreParams) error {
   604  	end, err := endSegment(p.TxnData, p.Thread, p.Start, p.Now)
   605  	if nil != err {
   606  		return err
   607  	}
   608  	if p.Operation == "" {
   609  		p.Operation = datastoreOperationUnknown
   610  	}
   611  	if p.Product == "" {
   612  		p.Product = datastoreProductUnknown
   613  	}
   614  	if p.Host == "" && p.PortPathOrID != "" {
   615  		p.Host = unknownDatastoreHost
   616  	}
   617  	if p.PortPathOrID == "" && p.Host != "" {
   618  		p.PortPathOrID = unknownDatastorePortPathOrID
   619  	}
   620  	if _, ok := hostsToReplace[p.Host]; ok {
   621  		p.Host = ThisHost
   622  	}
   623  
   624  	// We still want to create a slowQuery if the consumer has not provided
   625  	// a Query string (or it has been removed by LASP) since the stack trace
   626  	// has value.
   627  	if p.ParameterizedQuery == "" {
   628  		collection := p.Collection
   629  		if "" == collection {
   630  			collection = "unknown"
   631  		}
   632  		p.ParameterizedQuery = fmt.Sprintf(`'%s' on '%s' using '%s'`,
   633  			p.Operation, collection, p.Product)
   634  	}
   635  
   636  	key := DatastoreMetricKey{
   637  		Product:      p.Product,
   638  		Collection:   p.Collection,
   639  		Operation:    p.Operation,
   640  		Host:         p.Host,
   641  		PortPathOrID: p.PortPathOrID,
   642  	}
   643  	if nil == p.TxnData.datastoreSegments {
   644  		p.TxnData.datastoreSegments = make(map[DatastoreMetricKey]*metricData)
   645  	}
   646  	p.TxnData.datastoreCallCount++
   647  	p.TxnData.datastoreDuration += end.duration
   648  	m := metricDataFromDuration(end.duration, end.exclusive)
   649  	if data, ok := p.TxnData.datastoreSegments[key]; ok {
   650  		data.aggregate(m)
   651  	} else {
   652  		// Use `new` in place of &m so that m is not
   653  		// automatically moved to the heap.
   654  		cpy := new(metricData)
   655  		*cpy = m
   656  		p.TxnData.datastoreSegments[key] = cpy
   657  	}
   658  
   659  	scopedMetric := datastoreScopedMetric(key)
   660  	// errors in QueryParameters must not stop the recording of the segment
   661  	queryParams, err := vetQueryParameters(p.QueryParameters)
   662  
   663  	if p.TxnData.TxnTrace.considerNode(end) {
   664  		attributes := end.attributes.copy()
   665  		attributes.addString(spanAttributeDBStatement, p.ParameterizedQuery)
   666  		attributes.addString(spanAttributeDBInstance, p.Database)
   667  		attributes.addString(spanAttributePeerAddress, datastoreSpanAddress(p.Host, p.PortPathOrID))
   668  		attributes.addString(spanAttributePeerHostname, p.Host)
   669  		if len(queryParams) > 0 {
   670  			attributes.add(spanAttributeQueryParameters, queryParams)
   671  		}
   672  		p.TxnData.saveTraceSegment(end, scopedMetric, attributes, "")
   673  	}
   674  
   675  	if p.TxnData.slowQueryWorthy(end.duration) {
   676  		if nil == p.TxnData.SlowQueries {
   677  			p.TxnData.SlowQueries = newSlowQueries(maxTxnSlowQueries)
   678  		}
   679  		p.TxnData.SlowQueries.observeInstance(slowQueryInstance{
   680  			Duration:           end.duration,
   681  			DatastoreMetric:    scopedMetric,
   682  			ParameterizedQuery: p.ParameterizedQuery,
   683  			QueryParameters:    queryParams,
   684  			Host:               p.Host,
   685  			PortPathOrID:       p.PortPathOrID,
   686  			DatabaseName:       p.Database,
   687  			StackTrace:         GetStackTrace(),
   688  		})
   689  	}
   690  
   691  	if evt := end.spanEvent(); evt != nil {
   692  		evt.Name = scopedMetric
   693  		evt.Category = spanCategoryDatastore
   694  		evt.Kind = "client"
   695  		evt.Component = p.Product
   696  		evt.Attributes.addString(spanAttributeDBStatement, p.ParameterizedQuery)
   697  		evt.Attributes.addString(spanAttributeDBInstance, p.Database)
   698  		evt.Attributes.addString(spanAttributePeerAddress, datastoreSpanAddress(p.Host, p.PortPathOrID))
   699  		evt.Attributes.addString(spanAttributePeerHostname, p.Host)
   700  		evt.Attributes.addString(spanAttributeDBCollection, p.Collection)
   701  		p.TxnData.saveSpanEvent(evt)
   702  	}
   703  
   704  	return err
   705  }
   706  
   707  // MergeBreakdownMetrics creates segment metrics.
   708  func MergeBreakdownMetrics(t *TxnData, metrics *metricTable) {
   709  	scope := t.FinalName
   710  	isWeb := t.IsWeb
   711  	// Custom Segment Metrics
   712  	for key, data := range t.customSegments {
   713  		name := customSegmentMetric(key)
   714  		// Unscoped
   715  		metrics.add(name, "", *data, unforced)
   716  		// Scoped
   717  		metrics.add(name, scope, *data, unforced)
   718  	}
   719  
   720  	// External Segment Metrics
   721  	for key, data := range t.externalSegments {
   722  		metrics.add(externalRollupMetric.all, "", *data, forced)
   723  		metrics.add(externalRollupMetric.webOrOther(isWeb), "", *data, forced)
   724  
   725  		hostMetric := externalHostMetric(key)
   726  		metrics.add(hostMetric, "", *data, unforced)
   727  		if "" != key.ExternalCrossProcessID && "" != key.ExternalTransactionName {
   728  			txnMetric := externalTransactionMetric(key)
   729  
   730  			// Unscoped CAT metrics
   731  			metrics.add(externalAppMetric(key), "", *data, unforced)
   732  			metrics.add(txnMetric, "", *data, unforced)
   733  		}
   734  
   735  		// Scoped External Metric
   736  		metrics.add(key.scopedMetric(), scope, *data, unforced)
   737  	}
   738  
   739  	// Datastore Segment Metrics
   740  	for key, data := range t.datastoreSegments {
   741  		metrics.add(datastoreRollupMetric.all, "", *data, forced)
   742  		metrics.add(datastoreRollupMetric.webOrOther(isWeb), "", *data, forced)
   743  
   744  		product := datastoreProductMetric(key)
   745  		metrics.add(product.all, "", *data, forced)
   746  		metrics.add(product.webOrOther(isWeb), "", *data, forced)
   747  
   748  		if key.Host != "" && key.PortPathOrID != "" {
   749  			instance := datastoreInstanceMetric(key)
   750  			metrics.add(instance, "", *data, unforced)
   751  		}
   752  
   753  		operation := datastoreOperationMetric(key)
   754  		metrics.add(operation, "", *data, unforced)
   755  
   756  		if "" != key.Collection {
   757  			statement := datastoreStatementMetric(key)
   758  
   759  			metrics.add(statement, "", *data, unforced)
   760  			metrics.add(statement, scope, *data, unforced)
   761  		} else {
   762  			metrics.add(operation, scope, *data, unforced)
   763  		}
   764  	}
   765  	// Message Segment Metrics
   766  	for key, data := range t.messageSegments {
   767  		metric := key.Name()
   768  		metrics.add(metric, scope, *data, unforced)
   769  		metrics.add(metric, "", *data, unforced)
   770  	}
   771  }