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) {}