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 }