github.com/newrelic/go-agent@v3.26.0+incompatible/internal_txn_test.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package newrelic 5 6 import ( 7 "testing" 8 "time" 9 10 "github.com/newrelic/go-agent/internal" 11 "github.com/newrelic/go-agent/internal/cat" 12 "github.com/newrelic/go-agent/internal/sysinfo" 13 ) 14 15 func TestShouldSaveTrace(t *testing.T) { 16 for _, tc := range []struct { 17 name string 18 expected bool 19 synthetics bool 20 tracerEnabled bool 21 collectTraces bool 22 duration time.Duration 23 threshold time.Duration 24 }{ 25 { 26 name: "insufficient duration, all disabled", 27 expected: false, 28 synthetics: false, 29 tracerEnabled: false, 30 collectTraces: false, 31 duration: 1 * time.Second, 32 threshold: 2 * time.Second, 33 }, 34 { 35 name: "insufficient duration, only synthetics enabled", 36 expected: false, 37 synthetics: true, 38 tracerEnabled: false, 39 collectTraces: false, 40 duration: 1 * time.Second, 41 threshold: 2 * time.Second, 42 }, 43 { 44 name: "insufficient duration, only tracer enabled", 45 expected: false, 46 synthetics: false, 47 tracerEnabled: true, 48 collectTraces: false, 49 duration: 1 * time.Second, 50 threshold: 2 * time.Second, 51 }, 52 { 53 name: "insufficient duration, only collect traces enabled", 54 expected: false, 55 synthetics: false, 56 tracerEnabled: false, 57 collectTraces: true, 58 duration: 1 * time.Second, 59 threshold: 2 * time.Second, 60 }, 61 { 62 name: "insufficient duration, all normal flags enabled", 63 expected: false, 64 synthetics: false, 65 tracerEnabled: true, 66 collectTraces: true, 67 duration: 1 * time.Second, 68 threshold: 2 * time.Second, 69 }, 70 { 71 name: "insufficient duration, all flags enabled", 72 expected: true, 73 synthetics: true, 74 tracerEnabled: true, 75 collectTraces: true, 76 duration: 1 * time.Second, 77 threshold: 2 * time.Second, 78 }, 79 { 80 name: "sufficient duration, all disabled", 81 expected: false, 82 synthetics: false, 83 tracerEnabled: false, 84 collectTraces: false, 85 duration: 3 * time.Second, 86 threshold: 2 * time.Second, 87 }, 88 { 89 name: "sufficient duration, only synthetics enabled", 90 expected: false, 91 synthetics: true, 92 tracerEnabled: false, 93 collectTraces: false, 94 duration: 3 * time.Second, 95 threshold: 2 * time.Second, 96 }, 97 { 98 name: "sufficient duration, only tracer enabled", 99 expected: false, 100 synthetics: false, 101 tracerEnabled: true, 102 collectTraces: false, 103 duration: 3 * time.Second, 104 threshold: 2 * time.Second, 105 }, 106 { 107 name: "sufficient duration, only collect traces enabled", 108 expected: false, 109 synthetics: false, 110 tracerEnabled: false, 111 collectTraces: true, 112 duration: 3 * time.Second, 113 threshold: 2 * time.Second, 114 }, 115 { 116 name: "sufficient duration, all normal flags enabled", 117 expected: true, 118 synthetics: false, 119 tracerEnabled: true, 120 collectTraces: true, 121 duration: 3 * time.Second, 122 threshold: 2 * time.Second, 123 }, 124 { 125 name: "sufficient duration, all flags enabled", 126 expected: true, 127 synthetics: true, 128 tracerEnabled: true, 129 collectTraces: true, 130 duration: 3 * time.Second, 131 threshold: 2 * time.Second, 132 }, 133 } { 134 txn := &txn{} 135 136 cfg := NewConfig("my app", "0123456789012345678901234567890123456789") 137 cfg.TransactionTracer.Enabled = tc.tracerEnabled 138 cfg.TransactionTracer.Threshold.Duration = tc.threshold 139 cfg.TransactionTracer.Threshold.IsApdexFailing = false 140 reply := internal.ConnectReplyDefaults() 141 reply.CollectTraces = tc.collectTraces 142 txn.appRun = newAppRun(cfg, reply) 143 144 txn.Duration = tc.duration 145 if tc.synthetics { 146 txn.CrossProcess.Synthetics = &cat.SyntheticsHeader{} 147 txn.CrossProcess.SetSynthetics(tc.synthetics) 148 } 149 150 if actual := txn.shouldSaveTrace(); actual != tc.expected { 151 t.Errorf("%s: unexpected shouldSaveTrace value; expected %v; got %v", tc.name, tc.expected, actual) 152 } 153 } 154 } 155 156 func TestLazilyCalculateSampledTrue(t *testing.T) { 157 tx := &txn{} 158 tx.appRun = &appRun{} 159 tx.BetterCAT.Priority = 0.5 160 tx.sampledCalculated = false 161 tx.BetterCAT.Enabled = true 162 tx.Reply = &internal.ConnectReply{ 163 AdaptiveSampler: internal.SampleEverything{}, 164 } 165 out := tx.lazilyCalculateSampled() 166 if !out || !tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 1.5 { 167 t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) 168 } 169 tx.Reply.AdaptiveSampler = internal.SampleNothing{} 170 out = tx.lazilyCalculateSampled() 171 if !out || !tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 1.5 { 172 t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) 173 } 174 } 175 176 func TestLazilyCalculateSampledFalse(t *testing.T) { 177 tx := &txn{} 178 tx.appRun = &appRun{} 179 tx.BetterCAT.Priority = 0.5 180 tx.sampledCalculated = false 181 tx.BetterCAT.Enabled = true 182 tx.Reply = &internal.ConnectReply{ 183 AdaptiveSampler: internal.SampleNothing{}, 184 } 185 out := tx.lazilyCalculateSampled() 186 if out || tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 { 187 t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) 188 } 189 tx.Reply.AdaptiveSampler = internal.SampleEverything{} 190 out = tx.lazilyCalculateSampled() 191 if out || tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 { 192 t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) 193 } 194 } 195 196 func TestLazilyCalculateSampledCATDisabled(t *testing.T) { 197 tx := &txn{} 198 tx.appRun = &appRun{} 199 tx.BetterCAT.Priority = 0.5 200 tx.sampledCalculated = false 201 tx.BetterCAT.Enabled = false 202 tx.Reply = &internal.ConnectReply{ 203 AdaptiveSampler: internal.SampleEverything{}, 204 } 205 out := tx.lazilyCalculateSampled() 206 if out || tx.BetterCAT.Sampled || tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 { 207 t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) 208 } 209 out = tx.lazilyCalculateSampled() 210 if out || tx.BetterCAT.Sampled || tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 { 211 t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) 212 } 213 } 214 215 type expectTxnTimes struct { 216 txn *txn 217 testName string 218 start time.Time 219 stop time.Time 220 duration time.Duration 221 totalTime time.Duration 222 } 223 224 func TestTransactionDurationTotalTime(t *testing.T) { 225 // These tests touch internal txn structures rather than the public API: 226 // Testing duration and total time is tough because our API functions do 227 // not take fixed times. 228 start := time.Now() 229 testTxnTimes := func(expect expectTxnTimes) { 230 if expect.txn.Start != expect.start { 231 t.Error("start time", expect.testName, expect.txn.Start, expect.start) 232 } 233 if expect.txn.Stop != expect.stop { 234 t.Error("stop time", expect.testName, expect.txn.Stop, expect.stop) 235 } 236 if expect.txn.Duration != expect.duration { 237 t.Error("duration", expect.testName, expect.txn.Duration, expect.duration) 238 } 239 if expect.txn.TotalTime != expect.totalTime { 240 t.Error("total time", expect.testName, expect.txn.TotalTime, expect.totalTime) 241 } 242 } 243 244 // Basic transaction with no async activity. 245 tx := &txn{} 246 tx.markStart(start) 247 segmentStart := internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(1*time.Second)) 248 internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(2*time.Second), "name") 249 tx.markEnd(start.Add(3*time.Second), &tx.mainThread) 250 testTxnTimes(expectTxnTimes{ 251 txn: tx, 252 testName: "basic transaction", 253 start: start, 254 stop: start.Add(3 * time.Second), 255 duration: 3 * time.Second, 256 totalTime: 3 * time.Second, 257 }) 258 259 // Transaction with async activity. 260 tx = &txn{} 261 tx.markStart(start) 262 segmentStart = internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(1*time.Second)) 263 internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(2*time.Second), "name") 264 asyncThread := createThread(tx) 265 asyncSegmentStart := internal.StartSegment(&tx.TxnData, asyncThread, start.Add(1*time.Second)) 266 internal.EndBasicSegment(&tx.TxnData, asyncThread, asyncSegmentStart, start.Add(2*time.Second), "name") 267 tx.markEnd(start.Add(3*time.Second), &tx.mainThread) 268 testTxnTimes(expectTxnTimes{ 269 txn: tx, 270 testName: "transaction with async activity", 271 start: start, 272 stop: start.Add(3 * time.Second), 273 duration: 3 * time.Second, 274 totalTime: 4 * time.Second, 275 }) 276 277 // Transaction ended on async thread. 278 tx = &txn{} 279 tx.markStart(start) 280 segmentStart = internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(1*time.Second)) 281 internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(2*time.Second), "name") 282 asyncThread = createThread(tx) 283 asyncSegmentStart = internal.StartSegment(&tx.TxnData, asyncThread, start.Add(1*time.Second)) 284 internal.EndBasicSegment(&tx.TxnData, asyncThread, asyncSegmentStart, start.Add(2*time.Second), "name") 285 tx.markEnd(start.Add(3*time.Second), asyncThread) 286 testTxnTimes(expectTxnTimes{ 287 txn: tx, 288 testName: "transaction ended on async thread", 289 start: start, 290 stop: start.Add(3 * time.Second), 291 duration: 3 * time.Second, 292 totalTime: 4 * time.Second, 293 }) 294 295 // Duration exceeds TotalTime. 296 tx = &txn{} 297 tx.markStart(start) 298 segmentStart = internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(0*time.Second)) 299 internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(1*time.Second), "name") 300 asyncThread = createThread(tx) 301 asyncSegmentStart = internal.StartSegment(&tx.TxnData, asyncThread, start.Add(2*time.Second)) 302 internal.EndBasicSegment(&tx.TxnData, asyncThread, asyncSegmentStart, start.Add(3*time.Second), "name") 303 tx.markEnd(start.Add(3*time.Second), asyncThread) 304 testTxnTimes(expectTxnTimes{ 305 txn: tx, 306 testName: "TotalTime should be at least Duration", 307 start: start, 308 stop: start.Add(3 * time.Second), 309 duration: 3 * time.Second, 310 totalTime: 3 * time.Second, 311 }) 312 } 313 314 func TestGetTraceMetadataDistributedTracingDisabled(t *testing.T) { 315 replyfn := func(reply *internal.ConnectReply) { 316 reply.AdaptiveSampler = internal.SampleEverything{} 317 } 318 cfgfn := func(cfg *Config) { 319 cfg.DistributedTracer.Enabled = false 320 } 321 app := testApp(replyfn, cfgfn, t) 322 txn := app.StartTransaction("hello", nil, nil) 323 metadata := txn.GetTraceMetadata() 324 if metadata.SpanID != "" { 325 t.Error(metadata.SpanID) 326 } 327 if metadata.TraceID != "" { 328 t.Error(metadata.TraceID) 329 } 330 } 331 332 func TestGetTraceMetadataSuccess(t *testing.T) { 333 replyfn := func(reply *internal.ConnectReply) { 334 reply.AdaptiveSampler = internal.SampleEverything{} 335 reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) 336 } 337 cfgfn := func(cfg *Config) { 338 cfg.DistributedTracer.Enabled = true 339 } 340 app := testApp(replyfn, cfgfn, t) 341 txn := app.StartTransaction("hello", nil, nil) 342 metadata := txn.GetTraceMetadata() 343 if metadata.SpanID != "bcfb32e050b264b8" { 344 t.Error(metadata.SpanID) 345 } 346 if metadata.TraceID != "d9466896a525ccbf" { 347 t.Error(metadata.TraceID) 348 } 349 StartSegment(txn, "name") 350 // Span id should be different now that a segment has started. 351 metadata = txn.GetTraceMetadata() 352 if metadata.SpanID != "0e97aeb2f79d5d27" { 353 t.Error(metadata.SpanID) 354 } 355 if metadata.TraceID != "d9466896a525ccbf" { 356 t.Error(metadata.TraceID) 357 } 358 } 359 360 func TestGetTraceMetadataEnded(t *testing.T) { 361 // Test that GetTraceMetadata returns empty strings if the transaction 362 // has been finished. 363 replyfn := func(reply *internal.ConnectReply) { 364 reply.AdaptiveSampler = internal.SampleEverything{} 365 reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) 366 } 367 cfgfn := func(cfg *Config) { 368 cfg.DistributedTracer.Enabled = true 369 } 370 app := testApp(replyfn, cfgfn, t) 371 txn := app.StartTransaction("hello", nil, nil) 372 txn.End() 373 metadata := txn.GetTraceMetadata() 374 if metadata.SpanID != "" { 375 t.Error(metadata.SpanID) 376 } 377 if metadata.TraceID != "" { 378 t.Error(metadata.TraceID) 379 } 380 } 381 382 func TestGetTraceMetadataNotSampled(t *testing.T) { 383 replyfn := func(reply *internal.ConnectReply) { 384 reply.AdaptiveSampler = internal.SampleNothing{} 385 reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) 386 } 387 cfgfn := func(cfg *Config) { 388 cfg.DistributedTracer.Enabled = true 389 } 390 app := testApp(replyfn, cfgfn, t) 391 txn := app.StartTransaction("hello", nil, nil) 392 metadata := txn.GetTraceMetadata() 393 if metadata.SpanID != "" { 394 t.Error(metadata.SpanID) 395 } 396 if metadata.TraceID != "d9466896a525ccbf" { 397 t.Error(metadata.TraceID) 398 } 399 } 400 401 func TestGetTraceMetadataSpanEventsDisabled(t *testing.T) { 402 replyfn := func(reply *internal.ConnectReply) { 403 reply.AdaptiveSampler = internal.SampleEverything{} 404 reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) 405 } 406 cfgfn := func(cfg *Config) { 407 cfg.DistributedTracer.Enabled = true 408 cfg.SpanEvents.Enabled = false 409 } 410 app := testApp(replyfn, cfgfn, t) 411 txn := app.StartTransaction("hello", nil, nil) 412 metadata := txn.GetTraceMetadata() 413 if metadata.SpanID != "" { 414 t.Error(metadata.SpanID) 415 } 416 if metadata.TraceID != "d9466896a525ccbf" { 417 t.Error(metadata.TraceID) 418 } 419 } 420 421 func TestGetTraceMetadataInboundPayload(t *testing.T) { 422 replyfn := func(reply *internal.ConnectReply) { 423 reply.AdaptiveSampler = internal.SampleEverything{} 424 reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) 425 reply.AccountID = "account-id" 426 reply.TrustedAccountKey = "trust-key" 427 reply.PrimaryAppID = "app-id" 428 } 429 cfgfn := func(cfg *Config) { 430 cfg.DistributedTracer.Enabled = true 431 } 432 app := testApp(replyfn, cfgfn, t) 433 payload := app.StartTransaction("hello", nil, nil).CreateDistributedTracePayload() 434 p := payload.(internal.Payload) 435 p.TracedID = "trace-id" 436 437 txn := app.StartTransaction("hello", nil, nil) 438 err := txn.AcceptDistributedTracePayload(TransportHTTP, p) 439 if nil != err { 440 t.Error(err) 441 } 442 metadata := txn.GetTraceMetadata() 443 if metadata.SpanID != "9d2c19bd03daf755" { 444 t.Error(metadata.SpanID) 445 } 446 if metadata.TraceID != "trace-id" { 447 t.Error(metadata.TraceID) 448 } 449 } 450 451 func TestGetLinkingMetadata(t *testing.T) { 452 replyfn := func(reply *internal.ConnectReply) { 453 reply.AdaptiveSampler = internal.SampleEverything{} 454 reply.EntityGUID = "entities-are-guid" 455 reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) 456 } 457 cfgfn := func(cfg *Config) { 458 cfg.AppName = "app-name" 459 cfg.DistributedTracer.Enabled = true 460 } 461 app := testApp(replyfn, cfgfn, t) 462 txn := app.StartTransaction("hello", nil, nil) 463 464 metadata := txn.GetLinkingMetadata() 465 host, _ := sysinfo.Hostname() 466 if metadata.TraceID != "d9466896a525ccbf" { 467 t.Error("wrong TraceID:", metadata.TraceID) 468 } 469 if metadata.SpanID != "bcfb32e050b264b8" { 470 t.Error("wrong SpanID:", metadata.SpanID) 471 } 472 if metadata.EntityName != "app-name" { 473 t.Error("wrong EntityName:", metadata.EntityName) 474 } 475 if metadata.EntityType != "SERVICE" { 476 t.Error("wrong EntityType:", metadata.EntityType) 477 } 478 if metadata.EntityGUID != "entities-are-guid" { 479 t.Error("wrong EntityGUID:", metadata.EntityGUID) 480 } 481 if metadata.Hostname != host { 482 t.Error("wrong Hostname:", metadata.Hostname) 483 } 484 } 485 486 func TestGetLinkingMetadataAppNames(t *testing.T) { 487 testcases := []struct { 488 appName string 489 expected string 490 }{ 491 {appName: "one-name", expected: "one-name"}, 492 {appName: "one-name;two-name;three-name", expected: "one-name"}, 493 {appName: "", expected: ""}, 494 } 495 496 for _, test := range testcases { 497 cfgfn := func(cfg *Config) { 498 cfg.AppName = test.appName 499 } 500 app := testApp(nil, cfgfn, t) 501 txn := app.StartTransaction("hello", nil, nil) 502 503 metadata := txn.GetLinkingMetadata() 504 if metadata.EntityName != test.expected { 505 t.Errorf("wrong EntityName, actual=%s expected=%s", metadata.EntityName, test.expected) 506 } 507 } 508 } 509 510 func TestIsSampledFalse(t *testing.T) { 511 replyfn := func(reply *internal.ConnectReply) { 512 reply.AdaptiveSampler = internal.SampleNothing{} 513 } 514 cfgfn := func(cfg *Config) { 515 cfg.DistributedTracer.Enabled = true 516 } 517 app := testApp(replyfn, cfgfn, t) 518 txn := app.StartTransaction("hello", nil, nil) 519 sampled := txn.IsSampled() 520 if sampled == true { 521 t.Error("txn should not be sampled") 522 } 523 } 524 525 func TestIsSampledTrue(t *testing.T) { 526 replyfn := func(reply *internal.ConnectReply) { 527 reply.AdaptiveSampler = internal.SampleEverything{} 528 } 529 cfgfn := func(cfg *Config) { 530 cfg.DistributedTracer.Enabled = true 531 } 532 app := testApp(replyfn, cfgfn, t) 533 txn := app.StartTransaction("hello", nil, nil) 534 sampled := txn.IsSampled() 535 if sampled == false { 536 t.Error("txn should be sampled") 537 } 538 } 539 540 func TestIsSampledEnded(t *testing.T) { 541 // Test that Transaction.IsSampled returns false if the transaction has 542 // already ended. 543 replyfn := func(reply *internal.ConnectReply) { 544 reply.AdaptiveSampler = internal.SampleEverything{} 545 } 546 cfgfn := func(cfg *Config) { 547 cfg.DistributedTracer.Enabled = true 548 } 549 app := testApp(replyfn, cfgfn, t) 550 txn := app.StartTransaction("hello", nil, nil) 551 txn.End() 552 sampled := txn.IsSampled() 553 if sampled == true { 554 t.Error("finished txn should not be sampled") 555 } 556 }