github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/tracing.go (about)

     1  package internal
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"time"
     9  
    10  	"github.com/lulzWill/go-agent/internal/cat"
    11  	"github.com/lulzWill/go-agent/internal/sysinfo"
    12  )
    13  
    14  // TxnEvent represents a transaction.
    15  // https://source.datanerd.us/agents/agent-specs/blob/master/Transaction-Events-PORTED.md
    16  // https://newrelic.atlassian.net/wiki/display/eng/Agent+Support+for+Synthetics%3A+Forced+Transaction+Traces+and+Analytic+Events
    17  type TxnEvent struct {
    18  	FinalName string
    19  	Start     time.Time
    20  	Duration  time.Duration
    21  	Queuing   time.Duration
    22  	Zone      ApdexZone
    23  	Attrs     *Attributes
    24  	DatastoreExternalTotals
    25  	// CleanURL is not used in txn events, but is used in traced errors which embed TxnEvent.
    26  	CleanURL     string
    27  	CrossProcess TxnCrossProcess
    28  }
    29  
    30  // TxnData contains the recorded data of a transaction.
    31  type TxnData struct {
    32  	TxnEvent
    33  	IsWeb          bool
    34  	Name           string    // Work in progress name.
    35  	Errors         TxnErrors // Lazily initialized.
    36  	Stop           time.Time
    37  	ApdexThreshold time.Duration
    38  	Exclusive      time.Duration
    39  
    40  	finishedChildren time.Duration
    41  	stamp            segmentStamp
    42  	stack            []segmentFrame
    43  
    44  	customSegments    map[string]*metricData
    45  	datastoreSegments map[DatastoreMetricKey]*metricData
    46  	externalSegments  map[externalMetricKey]*metricData
    47  
    48  	TxnTrace
    49  
    50  	SlowQueriesEnabled bool
    51  	SlowQueryThreshold time.Duration
    52  	SlowQueries        *slowQueries
    53  }
    54  
    55  type segmentStamp uint64
    56  
    57  type segmentTime struct {
    58  	Stamp segmentStamp
    59  	Time  time.Time
    60  }
    61  
    62  // SegmentStartTime is embedded into the top level segments (rather than
    63  // segmentTime) to minimize the structure sizes to minimize allocations.
    64  type SegmentStartTime struct {
    65  	Stamp segmentStamp
    66  	Depth int
    67  }
    68  
    69  type segmentFrame struct {
    70  	segmentTime
    71  	children time.Duration
    72  }
    73  
    74  type segmentEnd struct {
    75  	start     segmentTime
    76  	stop      segmentTime
    77  	duration  time.Duration
    78  	exclusive time.Duration
    79  }
    80  
    81  const (
    82  	datastoreProductUnknown   = "Unknown"
    83  	datastoreOperationUnknown = "other"
    84  )
    85  
    86  // HasErrors indicates whether the transaction had errors.
    87  func (t *TxnData) HasErrors() bool {
    88  	return len(t.Errors) > 0
    89  }
    90  
    91  func (t *TxnData) time(now time.Time) segmentTime {
    92  	// Update the stamp before using it so that a 0 stamp can be special.
    93  	t.stamp++
    94  	return segmentTime{
    95  		Time:  now,
    96  		Stamp: t.stamp,
    97  	}
    98  }
    99  
   100  // TracerRootChildren is used to calculate a transaction's exclusive duration.
   101  func TracerRootChildren(t *TxnData) time.Duration {
   102  	var lostChildren time.Duration
   103  	for i := 0; i < len(t.stack); i++ {
   104  		lostChildren += t.stack[i].children
   105  	}
   106  	return t.finishedChildren + lostChildren
   107  }
   108  
   109  // StartSegment begins a segment.
   110  func StartSegment(t *TxnData, now time.Time) SegmentStartTime {
   111  	tm := t.time(now)
   112  	t.stack = append(t.stack, segmentFrame{
   113  		segmentTime: tm,
   114  		children:    0,
   115  	})
   116  
   117  	return SegmentStartTime{
   118  		Stamp: tm.Stamp,
   119  		Depth: len(t.stack) - 1,
   120  	}
   121  }
   122  
   123  var (
   124  	errMalformedSegment = errors.New("segment identifier malformed: perhaps unsafe code has modified it?")
   125  	errSegmentOrder     = errors.New(`improper segment use: the Transaction must be used ` +
   126  		`in a single goroutine and segments must be ended in "last started first ended" order: ` +
   127  		`see https://github.com/lulzWill/go-agent/blob/master/GUIDE.md#segments`)
   128  )
   129  
   130  func endSegment(t *TxnData, start SegmentStartTime, now time.Time) (segmentEnd, error) {
   131  	if 0 == start.Stamp {
   132  		return segmentEnd{}, errMalformedSegment
   133  	}
   134  	if start.Depth >= len(t.stack) {
   135  		return segmentEnd{}, errSegmentOrder
   136  	}
   137  	if start.Depth < 0 {
   138  		return segmentEnd{}, errMalformedSegment
   139  	}
   140  	if start.Stamp != t.stack[start.Depth].Stamp {
   141  		return segmentEnd{}, errSegmentOrder
   142  	}
   143  
   144  	var children time.Duration
   145  	for i := start.Depth; i < len(t.stack); i++ {
   146  		children += t.stack[i].children
   147  	}
   148  	s := segmentEnd{
   149  		stop:  t.time(now),
   150  		start: t.stack[start.Depth].segmentTime,
   151  	}
   152  	if s.stop.Time.After(s.start.Time) {
   153  		s.duration = s.stop.Time.Sub(s.start.Time)
   154  	}
   155  	if s.duration > children {
   156  		s.exclusive = s.duration - children
   157  	}
   158  
   159  	// Note that we expect (depth == (len(t.stack) - 1)).  However, if
   160  	// (depth < (len(t.stack) - 1)), that's ok: could be a panic popped
   161  	// some stack frames (and the consumer was not using defer).
   162  
   163  	if 0 == start.Depth {
   164  		t.finishedChildren += s.duration
   165  	} else {
   166  		t.stack[start.Depth-1].children += s.duration
   167  	}
   168  
   169  	t.stack = t.stack[0:start.Depth]
   170  
   171  	return s, nil
   172  }
   173  
   174  // EndBasicSegment ends a basic segment.
   175  func EndBasicSegment(t *TxnData, start SegmentStartTime, now time.Time, name string) error {
   176  	end, err := endSegment(t, start, now)
   177  	if nil != err {
   178  		return err
   179  	}
   180  	if nil == t.customSegments {
   181  		t.customSegments = make(map[string]*metricData)
   182  	}
   183  	m := metricDataFromDuration(end.duration, end.exclusive)
   184  	if data, ok := t.customSegments[name]; ok {
   185  		data.aggregate(m)
   186  	} else {
   187  		// Use `new` in place of &m so that m is not
   188  		// automatically moved to the heap.
   189  		cpy := new(metricData)
   190  		*cpy = m
   191  		t.customSegments[name] = cpy
   192  	}
   193  
   194  	if t.TxnTrace.considerNode(end) {
   195  		t.TxnTrace.witnessNode(end, customSegmentMetric(name), nil)
   196  	}
   197  
   198  	return nil
   199  }
   200  
   201  // EndExternalSegment ends an external segment.
   202  func EndExternalSegment(t *TxnData, start SegmentStartTime, now time.Time, u *url.URL, resp *http.Response) error {
   203  	end, err := endSegment(t, start, now)
   204  	if nil != err {
   205  		return err
   206  	}
   207  
   208  	host := HostFromURL(u)
   209  	if "" == host {
   210  		host = "unknown"
   211  	}
   212  
   213  	var appData *cat.AppDataHeader
   214  	if resp != nil {
   215  		appData, err = t.CrossProcess.ParseAppData(HTTPHeaderToAppData(resp.Header))
   216  		if err != nil {
   217  			return err
   218  		}
   219  	}
   220  
   221  	var crossProcessID string
   222  	var transactionName string
   223  	var transactionGUID string
   224  	if appData != nil {
   225  		crossProcessID = appData.CrossProcessID
   226  		transactionName = appData.TransactionName
   227  		transactionGUID = appData.TransactionGUID
   228  	}
   229  
   230  	key := externalMetricKey{
   231  		Host: host,
   232  		ExternalCrossProcessID:  crossProcessID,
   233  		ExternalTransactionName: transactionName,
   234  	}
   235  	if nil == t.externalSegments {
   236  		t.externalSegments = make(map[externalMetricKey]*metricData)
   237  	}
   238  	t.externalCallCount++
   239  	t.externalDuration += end.duration
   240  	m := metricDataFromDuration(end.duration, end.exclusive)
   241  	if data, ok := t.externalSegments[key]; ok {
   242  		data.aggregate(m)
   243  	} else {
   244  		// Use `new` in place of &m so that m is not
   245  		// automatically moved to the heap.
   246  		cpy := new(metricData)
   247  		*cpy = m
   248  		t.externalSegments[key] = cpy
   249  	}
   250  
   251  	if t.TxnTrace.considerNode(end) {
   252  		t.TxnTrace.witnessNode(end, externalHostMetric(key), &traceNodeParams{
   253  			CleanURL:        SafeURL(u),
   254  			TransactionGUID: transactionGUID,
   255  		})
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  // EndDatastoreParams contains the parameters for EndDatastoreSegment.
   262  type EndDatastoreParams struct {
   263  	Tracer             *TxnData
   264  	Start              SegmentStartTime
   265  	Now                time.Time
   266  	Product            string
   267  	Collection         string
   268  	Operation          string
   269  	ParameterizedQuery string
   270  	QueryParameters    map[string]interface{}
   271  	Host               string
   272  	PortPathOrID       string
   273  	Database           string
   274  }
   275  
   276  const (
   277  	unknownDatastoreHost         = "unknown"
   278  	unknownDatastorePortPathOrID = "unknown"
   279  )
   280  
   281  var (
   282  	// ThisHost is the system hostname.
   283  	ThisHost = func() string {
   284  		if h, err := sysinfo.Hostname(); nil == err {
   285  			return h
   286  		}
   287  		return unknownDatastoreHost
   288  	}()
   289  	hostsToReplace = map[string]struct{}{
   290  		"localhost":       {},
   291  		"127.0.0.1":       {},
   292  		"0.0.0.0":         {},
   293  		"0:0:0:0:0:0:0:1": {},
   294  		"::1":             {},
   295  		"0:0:0:0:0:0:0:0": {},
   296  		"::":              {},
   297  	}
   298  )
   299  
   300  func (t TxnData) slowQueryWorthy(d time.Duration) bool {
   301  	return t.SlowQueriesEnabled && (d >= t.SlowQueryThreshold)
   302  }
   303  
   304  // EndDatastoreSegment ends a datastore segment.
   305  func EndDatastoreSegment(p EndDatastoreParams) error {
   306  	end, err := endSegment(p.Tracer, p.Start, p.Now)
   307  	if nil != err {
   308  		return err
   309  	}
   310  	if p.Operation == "" {
   311  		p.Operation = datastoreOperationUnknown
   312  	}
   313  	if p.Product == "" {
   314  		p.Product = datastoreProductUnknown
   315  	}
   316  	if p.Host == "" && p.PortPathOrID != "" {
   317  		p.Host = unknownDatastoreHost
   318  	}
   319  	if p.PortPathOrID == "" && p.Host != "" {
   320  		p.PortPathOrID = unknownDatastorePortPathOrID
   321  	}
   322  	if _, ok := hostsToReplace[p.Host]; ok {
   323  		p.Host = ThisHost
   324  	}
   325  
   326  	// We still want to create a slowQuery if the consumer has not provided
   327  	// a Query string (or it has been removed by LASP) since the stack trace
   328  	// has value.
   329  	if p.ParameterizedQuery == "" {
   330  		collection := p.Collection
   331  		if "" == collection {
   332  			collection = "unknown"
   333  		}
   334  		p.ParameterizedQuery = fmt.Sprintf(`'%s' on '%s' using '%s'`,
   335  			p.Operation, collection, p.Product)
   336  	}
   337  
   338  	key := DatastoreMetricKey{
   339  		Product:      p.Product,
   340  		Collection:   p.Collection,
   341  		Operation:    p.Operation,
   342  		Host:         p.Host,
   343  		PortPathOrID: p.PortPathOrID,
   344  	}
   345  	if nil == p.Tracer.datastoreSegments {
   346  		p.Tracer.datastoreSegments = make(map[DatastoreMetricKey]*metricData)
   347  	}
   348  	p.Tracer.datastoreCallCount++
   349  	p.Tracer.datastoreDuration += end.duration
   350  	m := metricDataFromDuration(end.duration, end.exclusive)
   351  	if data, ok := p.Tracer.datastoreSegments[key]; ok {
   352  		data.aggregate(m)
   353  	} else {
   354  		// Use `new` in place of &m so that m is not
   355  		// automatically moved to the heap.
   356  		cpy := new(metricData)
   357  		*cpy = m
   358  		p.Tracer.datastoreSegments[key] = cpy
   359  	}
   360  
   361  	scopedMetric := datastoreScopedMetric(key)
   362  	queryParams := vetQueryParameters(p.QueryParameters)
   363  
   364  	if p.Tracer.TxnTrace.considerNode(end) {
   365  		p.Tracer.TxnTrace.witnessNode(end, scopedMetric, &traceNodeParams{
   366  			Host:            p.Host,
   367  			PortPathOrID:    p.PortPathOrID,
   368  			Database:        p.Database,
   369  			Query:           p.ParameterizedQuery,
   370  			queryParameters: queryParams,
   371  		})
   372  	}
   373  
   374  	if p.Tracer.slowQueryWorthy(end.duration) {
   375  		if nil == p.Tracer.SlowQueries {
   376  			p.Tracer.SlowQueries = newSlowQueries(maxTxnSlowQueries)
   377  		}
   378  		// Frames to skip:
   379  		//   this function
   380  		//   endDatastore
   381  		//   DatastoreSegment.End
   382  		skipFrames := 3
   383  		p.Tracer.SlowQueries.observeInstance(slowQueryInstance{
   384  			Duration:           end.duration,
   385  			DatastoreMetric:    scopedMetric,
   386  			ParameterizedQuery: p.ParameterizedQuery,
   387  			QueryParameters:    queryParams,
   388  			Host:               p.Host,
   389  			PortPathOrID:       p.PortPathOrID,
   390  			DatabaseName:       p.Database,
   391  			StackTrace:         GetStackTrace(skipFrames),
   392  		})
   393  	}
   394  
   395  	return nil
   396  }
   397  
   398  // MergeBreakdownMetrics creates segment metrics.
   399  func MergeBreakdownMetrics(t *TxnData, metrics *metricTable) {
   400  	scope := t.FinalName
   401  	isWeb := t.IsWeb
   402  	// Custom Segment Metrics
   403  	for key, data := range t.customSegments {
   404  		name := customSegmentMetric(key)
   405  		// Unscoped
   406  		metrics.add(name, "", *data, unforced)
   407  		// Scoped
   408  		metrics.add(name, scope, *data, unforced)
   409  	}
   410  
   411  	// External Segment Metrics
   412  	for key, data := range t.externalSegments {
   413  		metrics.add(externalRollupMetric.all, "", *data, forced)
   414  		metrics.add(externalRollupMetric.webOrOther(isWeb), "", *data, forced)
   415  
   416  		hostMetric := externalHostMetric(key)
   417  		metrics.add(hostMetric, "", *data, unforced)
   418  		if "" != key.ExternalCrossProcessID && "" != key.ExternalTransactionName {
   419  			txnMetric := externalTransactionMetric(key)
   420  
   421  			// Unscoped CAT metrics
   422  			metrics.add(externalAppMetric(key), "", *data, unforced)
   423  			metrics.add(txnMetric, "", *data, unforced)
   424  
   425  			// Scoped External Metric
   426  			metrics.add(txnMetric, scope, *data, unforced)
   427  		} else {
   428  			// Scoped External Metric
   429  			metrics.add(hostMetric, scope, *data, unforced)
   430  		}
   431  	}
   432  
   433  	// Datastore Segment Metrics
   434  	for key, data := range t.datastoreSegments {
   435  		metrics.add(datastoreRollupMetric.all, "", *data, forced)
   436  		metrics.add(datastoreRollupMetric.webOrOther(isWeb), "", *data, forced)
   437  
   438  		product := datastoreProductMetric(key)
   439  		metrics.add(product.all, "", *data, forced)
   440  		metrics.add(product.webOrOther(isWeb), "", *data, forced)
   441  
   442  		if key.Host != "" && key.PortPathOrID != "" {
   443  			instance := datastoreInstanceMetric(key)
   444  			metrics.add(instance, "", *data, unforced)
   445  		}
   446  
   447  		operation := datastoreOperationMetric(key)
   448  		metrics.add(operation, "", *data, unforced)
   449  
   450  		if "" != key.Collection {
   451  			statement := datastoreStatementMetric(key)
   452  
   453  			metrics.add(statement, "", *data, unforced)
   454  			metrics.add(statement, scope, *data, unforced)
   455  		} else {
   456  			metrics.add(operation, scope, *data, unforced)
   457  		}
   458  	}
   459  }