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

     1  package internal
     2  
     3  import (
     4  	"bytes"
     5  	"container/heap"
     6  	"encoding/json"
     7  	"sort"
     8  	"time"
     9  
    10  	"github.com/lulzWill/go-agent/internal/jsonx"
    11  )
    12  
    13  // See https://source.datanerd.us/agents/agent-specs/blob/master/Transaction-Trace-LEGACY.md
    14  
    15  type traceNodeHeap []traceNode
    16  
    17  // traceNodeParams is used for trace node parameters.  A struct is used in place
    18  // of a map[string]interface{} to facilitate testing and reduce JSON Marshal
    19  // overhead.  If too many fields get added here, it probably makes sense to
    20  // start using a map.  This struct is not embedded into traceNode to minimize
    21  // the size of traceNode:  Not all nodes will have parameters.
    22  type traceNodeParams struct {
    23  	StackTrace      StackTrace
    24  	CleanURL        string
    25  	Database        string
    26  	Host            string
    27  	PortPathOrID    string
    28  	Query           string
    29  	TransactionGUID string
    30  	queryParameters queryParameters
    31  }
    32  
    33  func (p *traceNodeParams) WriteJSON(buf *bytes.Buffer) {
    34  	w := jsonFieldsWriter{buf: buf}
    35  	buf.WriteByte('{')
    36  	if nil != p.StackTrace {
    37  		w.writerField("backtrace", p.StackTrace)
    38  	}
    39  	if "" != p.CleanURL {
    40  		w.stringField("uri", p.CleanURL)
    41  	}
    42  	if "" != p.Database {
    43  		w.stringField("database_name", p.Database)
    44  	}
    45  	if "" != p.Host {
    46  		w.stringField("host", p.Host)
    47  	}
    48  	if "" != p.PortPathOrID {
    49  		w.stringField("port_path_or_id", p.PortPathOrID)
    50  	}
    51  	if "" != p.Query {
    52  		w.stringField("query", p.Query)
    53  	}
    54  	if "" != p.TransactionGUID {
    55  		w.stringField("transaction_guid", p.TransactionGUID)
    56  	}
    57  	if nil != p.queryParameters {
    58  		w.writerField("query_parameters", p.queryParameters)
    59  	}
    60  	buf.WriteByte('}')
    61  }
    62  
    63  // MarshalJSON is used for testing.
    64  func (p *traceNodeParams) MarshalJSON() ([]byte, error) {
    65  	buf := &bytes.Buffer{}
    66  	p.WriteJSON(buf)
    67  	return buf.Bytes(), nil
    68  }
    69  
    70  type traceNode struct {
    71  	start    segmentTime
    72  	stop     segmentTime
    73  	duration time.Duration
    74  	params   *traceNodeParams
    75  	name     string
    76  }
    77  
    78  func (h traceNodeHeap) Len() int           { return len(h) }
    79  func (h traceNodeHeap) Less(i, j int) bool { return h[i].duration < h[j].duration }
    80  func (h traceNodeHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
    81  
    82  // Push and Pop are unused: only heap.Init and heap.Fix are used.
    83  func (h traceNodeHeap) Push(x interface{}) {}
    84  func (h traceNodeHeap) Pop() interface{}   { return nil }
    85  
    86  // TxnTrace contains the work in progress transaction trace.
    87  type TxnTrace struct {
    88  	Enabled             bool
    89  	SegmentThreshold    time.Duration
    90  	StackTraceThreshold time.Duration
    91  	nodes               traceNodeHeap
    92  	maxNodes            int
    93  }
    94  
    95  // considerNode exists to prevent unnecessary calls to witnessNode: constructing
    96  // the metric name and params map requires allocations.
    97  func (trace *TxnTrace) considerNode(end segmentEnd) bool {
    98  	return trace.Enabled && (end.duration >= trace.SegmentThreshold)
    99  }
   100  
   101  func (trace *TxnTrace) witnessNode(end segmentEnd, name string, params *traceNodeParams) {
   102  	node := traceNode{
   103  		start:    end.start,
   104  		stop:     end.stop,
   105  		duration: end.duration,
   106  		name:     name,
   107  		params:   params,
   108  	}
   109  	if !trace.considerNode(end) {
   110  		return
   111  	}
   112  	if trace.nodes == nil {
   113  		max := trace.maxNodes
   114  		if 0 == max {
   115  			max = maxTxnTraceNodes
   116  		}
   117  		trace.nodes = make(traceNodeHeap, 0, max)
   118  	}
   119  	if end.exclusive >= trace.StackTraceThreshold {
   120  		if node.params == nil {
   121  			p := new(traceNodeParams)
   122  			node.params = p
   123  		}
   124  		// skip the following stack frames:
   125  		//   this method
   126  		//   function in tracing.go      (EndBasicSegment, EndExternalSegment, EndDatastoreSegment)
   127  		//   function in internal_txn.go (endSegment, endExternal, endDatastore)
   128  		//   segment end method
   129  		skip := 4
   130  		node.params.StackTrace = GetStackTrace(skip)
   131  	}
   132  	if len(trace.nodes) < cap(trace.nodes) {
   133  		trace.nodes = append(trace.nodes, node)
   134  		if len(trace.nodes) == cap(trace.nodes) {
   135  			heap.Init(trace.nodes)
   136  		}
   137  		return
   138  	}
   139  	if node.duration <= trace.nodes[0].duration {
   140  		return
   141  	}
   142  	trace.nodes[0] = node
   143  	heap.Fix(trace.nodes, 0)
   144  }
   145  
   146  // HarvestTrace contains a finished transaction trace ready for serialization to
   147  // the collector.
   148  type HarvestTrace struct {
   149  	TxnEvent
   150  	Trace TxnTrace
   151  }
   152  
   153  type nodeDetails struct {
   154  	name          string
   155  	relativeStart time.Duration
   156  	relativeStop  time.Duration
   157  	params        *traceNodeParams
   158  }
   159  
   160  func printNodeStart(buf *bytes.Buffer, n nodeDetails) {
   161  	// time.Seconds() is intentionally not used here.  Millisecond
   162  	// precision is enough.
   163  	relativeStartMillis := n.relativeStart.Nanoseconds() / (1000 * 1000)
   164  	relativeStopMillis := n.relativeStop.Nanoseconds() / (1000 * 1000)
   165  
   166  	buf.WriteByte('[')
   167  	jsonx.AppendInt(buf, relativeStartMillis)
   168  	buf.WriteByte(',')
   169  	jsonx.AppendInt(buf, relativeStopMillis)
   170  	buf.WriteByte(',')
   171  	jsonx.AppendString(buf, n.name)
   172  	buf.WriteByte(',')
   173  	if nil == n.params {
   174  		buf.WriteString("{}")
   175  	} else {
   176  		n.params.WriteJSON(buf)
   177  	}
   178  	buf.WriteByte(',')
   179  	buf.WriteByte('[')
   180  }
   181  
   182  func printChildren(buf *bytes.Buffer, traceStart time.Time, nodes sortedTraceNodes, next int, stop segmentStamp) int {
   183  	firstChild := true
   184  	for next < len(nodes) && nodes[next].start.Stamp < stop {
   185  		if firstChild {
   186  			firstChild = false
   187  		} else {
   188  			buf.WriteByte(',')
   189  		}
   190  		printNodeStart(buf, nodeDetails{
   191  			name:          nodes[next].name,
   192  			relativeStart: nodes[next].start.Time.Sub(traceStart),
   193  			relativeStop:  nodes[next].stop.Time.Sub(traceStart),
   194  			params:        nodes[next].params,
   195  		})
   196  		next = printChildren(buf, traceStart, nodes, next+1, nodes[next].stop.Stamp)
   197  		buf.WriteString("]]")
   198  
   199  	}
   200  	return next
   201  }
   202  
   203  type sortedTraceNodes []*traceNode
   204  
   205  func (s sortedTraceNodes) Len() int           { return len(s) }
   206  func (s sortedTraceNodes) Less(i, j int) bool { return s[i].start.Stamp < s[j].start.Stamp }
   207  func (s sortedTraceNodes) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   208  
   209  // MarshalJSON prepares the trace in the JSON expected by the collector.
   210  func (trace *HarvestTrace) MarshalJSON() ([]byte, error) {
   211  	estimate := 100 * len(trace.Trace.nodes)
   212  	buf := bytes.NewBuffer(make([]byte, 0, estimate))
   213  
   214  	nodes := make(sortedTraceNodes, len(trace.Trace.nodes))
   215  	for i := 0; i < len(nodes); i++ {
   216  		nodes[i] = &trace.Trace.nodes[i]
   217  	}
   218  	sort.Sort(nodes)
   219  
   220  	buf.WriteByte('[') // begin trace
   221  
   222  	jsonx.AppendInt(buf, trace.Start.UnixNano()/1000)
   223  	buf.WriteByte(',')
   224  	jsonx.AppendFloat(buf, trace.Duration.Seconds()*1000.0)
   225  	buf.WriteByte(',')
   226  	jsonx.AppendString(buf, trace.FinalName)
   227  	buf.WriteByte(',')
   228  	jsonx.AppendString(buf, trace.CleanURL)
   229  	buf.WriteByte(',')
   230  
   231  	buf.WriteByte('[') // begin trace data
   232  
   233  	// If the trace string pool is used, insert another array here.
   234  
   235  	jsonx.AppendFloat(buf, 0.0) // unused timestamp
   236  	buf.WriteByte(',')          //
   237  	buf.WriteString("{}")       // unused: formerly request parameters
   238  	buf.WriteByte(',')          //
   239  	buf.WriteString("{}")       // unused: formerly custom parameters
   240  	buf.WriteByte(',')          //
   241  
   242  	printNodeStart(buf, nodeDetails{ // begin outer root
   243  		name:          "ROOT",
   244  		relativeStart: 0,
   245  		relativeStop:  trace.Duration,
   246  	})
   247  
   248  	printNodeStart(buf, nodeDetails{ // begin inner root
   249  		name:          trace.FinalName,
   250  		relativeStart: 0,
   251  		relativeStop:  trace.Duration,
   252  	})
   253  
   254  	if len(nodes) > 0 {
   255  		lastStopStamp := nodes[len(nodes)-1].stop.Stamp + 1
   256  		printChildren(buf, trace.Start, nodes, 0, lastStopStamp)
   257  	}
   258  
   259  	buf.WriteString("]]") // end outer root
   260  	buf.WriteString("]]") // end inner root
   261  
   262  	buf.WriteByte(',')
   263  	buf.WriteByte('{')
   264  	buf.WriteString(`"agentAttributes":`)
   265  	agentAttributesJSON(trace.Attrs, buf, destTxnTrace)
   266  	buf.WriteByte(',')
   267  	buf.WriteString(`"userAttributes":`)
   268  	userAttributesJSON(trace.Attrs, buf, destTxnTrace, nil)
   269  	buf.WriteByte(',')
   270  	buf.WriteString(`"intrinsics":`)
   271  	intrinsicsJSON(&trace.TxnEvent, buf)
   272  	buf.WriteByte('}')
   273  
   274  	// If the trace string pool is used, end another array here.
   275  
   276  	buf.WriteByte(']') // end trace data
   277  
   278  	buf.WriteByte(',')
   279  	if trace.CrossProcess.Used() && trace.CrossProcess.GUID != "" {
   280  		jsonx.AppendString(buf, trace.CrossProcess.GUID)
   281  	} else {
   282  		buf.WriteString(`""`)
   283  	}
   284  	buf.WriteByte(',')       //
   285  	buf.WriteString(`null`)  // reserved for future use
   286  	buf.WriteByte(',')       //
   287  	buf.WriteString(`false`) // ForcePersist is not yet supported
   288  	buf.WriteByte(',')       //
   289  	buf.WriteString(`null`)  // X-Ray sessions not supported
   290  	buf.WriteByte(',')       //
   291  
   292  	// Synthetics are supported:
   293  	if trace.CrossProcess.IsSynthetics() {
   294  		jsonx.AppendString(buf, trace.CrossProcess.Synthetics.ResourceID)
   295  	} else {
   296  		buf.WriteString(`""`)
   297  	}
   298  
   299  	buf.WriteByte(']') // end trace
   300  
   301  	return buf.Bytes(), nil
   302  }
   303  
   304  type txnTraceHeap []*HarvestTrace
   305  
   306  func (h *txnTraceHeap) isEmpty() bool {
   307  	return 0 == len(*h)
   308  }
   309  
   310  func newTxnTraceHeap(max int) *txnTraceHeap {
   311  	h := make(txnTraceHeap, 0, max)
   312  	heap.Init(&h)
   313  	return &h
   314  }
   315  
   316  // Implement sort.Interface.
   317  func (h txnTraceHeap) Len() int           { return len(h) }
   318  func (h txnTraceHeap) Less(i, j int) bool { return h[i].Duration < h[j].Duration }
   319  func (h txnTraceHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
   320  
   321  // Implement heap.Interface.
   322  func (h *txnTraceHeap) Push(x interface{}) { *h = append(*h, x.(*HarvestTrace)) }
   323  
   324  func (h *txnTraceHeap) Pop() interface{} {
   325  	old := *h
   326  	n := len(old)
   327  	x := old[n-1]
   328  	*h = old[0 : n-1]
   329  	return x
   330  }
   331  
   332  func (h *txnTraceHeap) isKeeper(t *HarvestTrace) bool {
   333  	if len(*h) < cap(*h) {
   334  		return true
   335  	}
   336  	return t.Duration >= (*h)[0].Duration
   337  }
   338  
   339  func (h *txnTraceHeap) addTxnTrace(t *HarvestTrace) {
   340  	if len(*h) < cap(*h) {
   341  		heap.Push(h, t)
   342  		return
   343  	}
   344  
   345  	if t.Duration <= (*h)[0].Duration {
   346  		return
   347  	}
   348  	heap.Pop(h)
   349  	heap.Push(h, t)
   350  }
   351  
   352  type harvestTraces struct {
   353  	regular    *txnTraceHeap
   354  	synthetics *txnTraceHeap
   355  }
   356  
   357  func newHarvestTraces() *harvestTraces {
   358  	return &harvestTraces{
   359  		regular:    newTxnTraceHeap(maxRegularTraces),
   360  		synthetics: newTxnTraceHeap(maxSyntheticsTraces),
   361  	}
   362  }
   363  
   364  func (traces *harvestTraces) Len() int {
   365  	return traces.regular.Len() + traces.synthetics.Len()
   366  }
   367  
   368  func (traces *harvestTraces) Witness(trace HarvestTrace) {
   369  	traceHeap := traces.regular
   370  	if trace.CrossProcess.IsSynthetics() {
   371  		traceHeap = traces.synthetics
   372  	}
   373  
   374  	if traceHeap.isKeeper(&trace) {
   375  		cpy := new(HarvestTrace)
   376  		*cpy = trace
   377  		traceHeap.addTxnTrace(cpy)
   378  	}
   379  }
   380  
   381  func (traces *harvestTraces) Data(agentRunID string, harvestStart time.Time) ([]byte, error) {
   382  	if traces.Len() == 0 {
   383  		return nil, nil
   384  	}
   385  
   386  	return json.Marshal([]interface{}{
   387  		agentRunID,
   388  		traces.slice(),
   389  	})
   390  }
   391  
   392  func (traces *harvestTraces) slice() []*HarvestTrace {
   393  	out := make([]*HarvestTrace, 0, traces.Len())
   394  	out = append(out, (*traces.regular)...)
   395  	out = append(out, (*traces.synthetics)...)
   396  
   397  	return out
   398  }
   399  
   400  func (traces *harvestTraces) MergeIntoHarvest(h *Harvest) {}