github.com/waldiirawan/apm-agent-go/v2@v2.2.2/breakdown_test.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package apm_test 19 20 import ( 21 "context" 22 "fmt" 23 "os" 24 "testing" 25 "time" 26 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 30 "github.com/waldiirawan/apm-agent-go/v2" 31 "github.com/waldiirawan/apm-agent-go/v2/apmtest" 32 "github.com/waldiirawan/apm-agent-go/v2/model" 33 "github.com/waldiirawan/apm-agent-go/v2/transport/transporttest" 34 ) 35 36 func TestBreakdownMetrics_NonSampled(t *testing.T) { 37 tracer, transport := transporttest.NewRecorderTracer() 38 defer tracer.Close() 39 40 // Non-sampled transactions and their dropped spans still attribute to breakdown metrics. 41 tracer.SetSampler(apm.NewRatioSampler(0)) 42 43 t0 := time.Now() 44 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 45 span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 46 span.Duration = 10 * time.Millisecond // t0 + 20ms 47 span.End() 48 tx.Duration = 30 * time.Millisecond 49 tx.End() 50 51 tracer.Flush(nil) 52 tracer.SendMetrics(nil) 53 54 assertBreakdownMetrics(t, []model.Metrics{ 55 spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond), 56 spanSelfTimeMetrics("test", "request", "db", "mysql", 1, 10*time.Millisecond), 57 }, payloadsBreakdownMetrics(transport)) 58 } 59 60 func TestBreakdownMetrics_SpanDropped(t *testing.T) { 61 tracer, transport := transporttest.NewRecorderTracer() 62 defer tracer.Close() 63 64 // Dropped spans still attribute to breakdown metrics. 65 tracer.SetMaxSpans(1) 66 67 t0 := time.Now() 68 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 69 var spans []*apm.Span 70 for i := 0; i < 2; i++ { 71 span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 72 spans = append(spans, span) 73 } 74 for _, span := range spans { 75 span.Duration = 10 * time.Millisecond // t0 + 20ms 76 span.End() 77 } 78 tx.Duration = 30 * time.Millisecond 79 tx.End() 80 81 tracer.Flush(nil) 82 tracer.SendMetrics(nil) 83 84 assertBreakdownMetrics(t, []model.Metrics{ 85 spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond), 86 spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 20*time.Millisecond), 87 }, payloadsBreakdownMetrics(transport)) 88 } 89 90 func TestBreakdownMetrics_MetricsLimit(t *testing.T) { 91 tracer, transport := transporttest.NewRecorderTracer() 92 defer tracer.Close() 93 94 var logger apmtest.RecordLogger 95 tracer.SetLogger(&logger) 96 97 for i := 0; i < 3000; i++ { 98 tx := tracer.StartTransaction(fmt.Sprintf("%d", i), "request") 99 tx.End() 100 } 101 tracer.Flush(nil) 102 tracer.SendMetrics(nil) 103 104 // Make sure there's just one warning logged. 105 var warnings []apmtest.LogRecord 106 for _, record := range logger.Records { 107 if record.Level == "warning" { 108 warnings = append(warnings, record) 109 } 110 } 111 require.Len(t, warnings, 1) 112 assert.Regexp(t, "The limit of 1000 breakdown (.|\n)*", warnings[0].Message) 113 114 // There should be 1000 breakdown metrics keys buckets retained 115 // in-memory. 116 metrics := payloadsBreakdownMetrics(transport) 117 assert.Len(t, metrics, 1000) 118 } 119 120 func TestBreakdownMetrics_TransactionDropped(t *testing.T) { 121 tracer, transport := transporttest.NewRecorderTracer() 122 defer tracer.Close() 123 124 // Send transactions until one gets dropped, so we can check that 125 // the dropped transactions are included in the breakdown. 126 var count int 127 for tracer.Stats().TransactionsDropped == 0 { 128 for i := 0; i < 1000; i++ { 129 tx := tracer.StartTransaction("test", "request") 130 tx.Duration = 10 * time.Millisecond 131 tx.End() 132 count++ 133 } 134 } 135 136 tracer.Flush(nil) 137 tracer.SendMetrics(nil) 138 139 assertBreakdownMetrics(t, []model.Metrics{ 140 spanSelfTimeMetrics("test", "request", "app", "", count, time.Duration(count)*10*time.Millisecond), 141 }, payloadsBreakdownMetrics(transport)) 142 } 143 144 func TestBreakdownMetrics_Disabled(t *testing.T) { 145 os.Setenv("ELASTIC_APM_BREAKDOWN_METRICS", "false") 146 defer os.Unsetenv("ELASTIC_APM_BREAKDOWN_METRICS") 147 148 tracer, transport := transporttest.NewRecorderTracer() 149 defer tracer.Close() 150 151 t0 := time.Now() 152 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 153 span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 154 span.Duration = 10 * time.Millisecond // t0 + 20ms 155 span.End() 156 tx.Duration = 30 * time.Millisecond 157 tx.End() 158 159 tracer.Flush(nil) 160 tracer.SendMetrics(nil) 161 162 assertBreakdownMetrics(t, []model.Metrics{}, payloadsBreakdownMetrics(transport)) 163 } 164 165 func TestBreakdownMetrics_AcceptanceTest1(t *testing.T) { 166 // total self type 167 // ██████████████████████████████ 30 30 transaction 168 // 10 20 30 169 170 tracer, transport := transporttest.NewRecorderTracer() 171 defer tracer.Close() 172 173 tx := tracer.StartTransaction("test", "request") 174 tx.Duration = 30 * time.Millisecond 175 tx.End() 176 177 tracer.Flush(nil) 178 tracer.SendMetrics(nil) 179 180 assertBreakdownMetrics(t, []model.Metrics{ 181 spanSelfTimeMetrics("test", "request", "app", "", 1, 30*time.Millisecond), 182 }, payloadsBreakdownMetrics(transport)) 183 } 184 185 func TestBreakdownMetrics_AcceptanceTest2(t *testing.T) { 186 // total self type 187 // ██████████░░░░░░░░░░██████████ 30 20 transaction 188 // └─────────██████████ 10 10 db.mysql 189 // 10 20 30 190 191 tracer, transport := transporttest.NewRecorderTracer() 192 defer tracer.Close() 193 194 t0 := time.Now() 195 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 196 span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 197 span.Duration = 10 * time.Millisecond // t0 + 20ms 198 span.End() 199 tx.Duration = 30 * time.Millisecond // t0 + 30ms 200 tx.End() 201 202 tracer.Flush(nil) 203 tracer.SendMetrics(nil) 204 205 assertBreakdownMetrics(t, []model.Metrics{ 206 spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond), 207 spanSelfTimeMetrics("test", "request", "db", "mysql", 1, 10*time.Millisecond), 208 }, payloadsBreakdownMetrics(transport)) 209 } 210 211 func TestBreakdownMetrics_AcceptanceTest3(t *testing.T) { 212 // total self type 213 // ██████████░░░░░░░░░░██████████ 30 20 transaction 214 // └─────────██████████ 10 10 app 215 // 10 20 30 216 217 tracer, transport := transporttest.NewRecorderTracer() 218 defer tracer.Close() 219 220 t0 := time.Now() 221 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 222 span := tx.StartSpanOptions("whatever", "app", apm.SpanOptions{ 223 Start: t0.Add(10 * time.Millisecond), 224 }) 225 span.Duration = 10 * time.Millisecond // t0 + 20ms 226 span.End() 227 tx.Duration = 30 * time.Millisecond // t0 + 30ms 228 tx.End() 229 230 tracer.Flush(nil) 231 tracer.SendMetrics(nil) 232 233 assertBreakdownMetrics(t, []model.Metrics{ 234 spanSelfTimeMetrics("test", "request", "app", "", 2, 30*time.Millisecond), 235 }, payloadsBreakdownMetrics(transport)) 236 } 237 238 func TestBreakdownMetrics_AcceptanceTest4(t *testing.T) { 239 // total self type 240 // ██████████░░░░░░░░░░██████████ 30 20 transaction 241 // └─────────██████████ 10 10 db.mysql 242 // └─────────██████████ 10 10 db.mysql 243 // 10 20 30 244 245 tracer, transport := transporttest.NewRecorderTracer() 246 defer tracer.Close() 247 248 t0 := time.Now() 249 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 250 span1 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 251 span2 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 252 span1.Duration = 10 * time.Millisecond // t0 + 20ms 253 span2.Duration = 10 * time.Millisecond // t0 + 20ms 254 span1.End() 255 span2.End() 256 tx.Duration = 30 * time.Millisecond // t0 + 30ms 257 tx.End() 258 259 tracer.Flush(nil) 260 tracer.SendMetrics(nil) 261 262 assertBreakdownMetrics(t, []model.Metrics{ 263 spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond), 264 spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 20*time.Millisecond), 265 }, payloadsBreakdownMetrics(transport)) 266 } 267 268 func TestBreakdownMetrics_AcceptanceTest5(t *testing.T) { 269 // total self type 270 // ██████████░░░░░░░░░░░░░░░█████ 30 15 transaction 271 // └─────────██████████ 10 10 db.mysql 272 // └──────────────██████████ 10 10 db.mysql 273 // 10 20 30 274 275 tracer, transport := transporttest.NewRecorderTracer() 276 defer tracer.Close() 277 278 t0 := time.Now() 279 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 280 span1 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 281 span2 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(15 * time.Millisecond)}) 282 span1.Duration = 10 * time.Millisecond // t0 + 20ms 283 span2.Duration = 10 * time.Millisecond // t0 + 25ms 284 span1.End() 285 span2.End() 286 tx.Duration = 30 * time.Millisecond // t0 + 30ms 287 tx.End() 288 289 tracer.Flush(nil) 290 tracer.SendMetrics(nil) 291 292 assertBreakdownMetrics(t, []model.Metrics{ 293 spanSelfTimeMetrics("test", "request", "app", "", 1, 15*time.Millisecond), 294 spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 20*time.Millisecond), 295 }, payloadsBreakdownMetrics(transport)) 296 } 297 298 func TestBreakdownMetrics_AcceptanceTest6(t *testing.T) { 299 // total self type 300 // █████░░░░░░░░░░░░░░░░░░░░█████ 30 15 transaction 301 // └────██████████ 10 10 db.mysql 302 // └──────────────██████████ 10 10 db.mysql 303 // 10 20 30 304 305 tracer, transport := transporttest.NewRecorderTracer() 306 defer tracer.Close() 307 308 t0 := time.Now() 309 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 310 span1 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(5 * time.Millisecond)}) 311 span1.Duration = 10 * time.Millisecond // t0 + 15ms 312 span1.End() 313 span2 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(15 * time.Millisecond)}) 314 span2.Duration = 10 * time.Millisecond // t0 + 25ms 315 span2.End() 316 tx.Duration = 30 * time.Millisecond // t0 + 30ms 317 tx.End() 318 319 tracer.Flush(nil) 320 tracer.SendMetrics(nil) 321 322 assertBreakdownMetrics(t, []model.Metrics{ 323 spanSelfTimeMetrics("test", "request", "app", "", 1, 10*time.Millisecond), 324 spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 20*time.Millisecond), 325 }, payloadsBreakdownMetrics(transport)) 326 } 327 328 func TestBreakdownMetrics_AcceptanceTest7(t *testing.T) { 329 // total self type 330 // ██████████░░░░░█████░░░░░█████ 30 15 transaction 331 // └─────────█████ 5 5 db.mysql 332 // └───────────────────█████ 5 5 db.mysql 333 // 10 20 30 334 335 tracer, transport := transporttest.NewRecorderTracer() 336 defer tracer.Close() 337 338 t0 := time.Now() 339 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 340 span1 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 341 span1.Duration = 5 * time.Millisecond // t0 + 15ms 342 span1.End() 343 span2 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(20 * time.Millisecond)}) 344 span2.Duration = 5 * time.Millisecond // t0 + 25ms 345 span2.End() 346 tx.Duration = 30 * time.Millisecond // t0 + 30ms 347 tx.End() 348 349 tracer.Flush(nil) 350 tracer.SendMetrics(nil) 351 352 assertBreakdownMetrics(t, []model.Metrics{ 353 spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond), 354 spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 10*time.Millisecond), 355 }, payloadsBreakdownMetrics(transport)) 356 } 357 358 func TestBreakdownMetrics_AcceptanceTest8(t *testing.T) { 359 // total self type 360 // ██████████░░░░░░░░░░██████████ 30 20 transaction 361 // └─────────█████░░░░░ 10 5 app 362 // └────██████████ 10 10 db.mysql 363 // 10 20 30 364 365 tracer, transport := transporttest.NewRecorderTracer() 366 defer tracer.Close() 367 368 t0 := time.Now() 369 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 370 ctx := apm.ContextWithTransaction(context.Background(), tx) 371 372 span1, ctx := apm.StartSpanOptions(ctx, "whatever", "app", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 373 span2, ctx := apm.StartSpanOptions(ctx, "whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(15 * time.Millisecond)}) 374 span1.Duration = 10 * time.Millisecond // t0 + 20ms 375 span2.Duration = 10 * time.Millisecond // t0 + 25ms 376 span1.End() 377 span2.End() 378 tx.Duration = 30 * time.Millisecond // t0 + 30ms 379 tx.End() 380 381 tracer.Flush(nil) 382 tracer.SendMetrics(nil) 383 384 assertBreakdownMetrics(t, []model.Metrics{ 385 spanSelfTimeMetrics("test", "request", "app", "", 2, 25*time.Millisecond), 386 spanSelfTimeMetrics("test", "request", "db", "mysql", 1, 10*time.Millisecond), 387 }, payloadsBreakdownMetrics(transport)) 388 } 389 390 func TestBreakdownMetrics_AcceptanceTest9(t *testing.T) { 391 // total self type 392 // ██████████░░░░░░░░░░ 20 20 transaction 393 // └─────────██████████░░░░░░░░░░ 20 10 app 394 // └─────────██████████ 10 10 db.mysql 395 // 10 20 30 396 397 tracer, transport := transporttest.NewRecorderTracer() 398 defer tracer.Close() 399 400 t0 := time.Now() 401 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 402 ctx := apm.ContextWithTransaction(context.Background(), tx) 403 404 span1, ctx := apm.StartSpanOptions(ctx, "whatever", "app", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 405 tx.Duration = 20 * time.Millisecond // t0 + 20ms 406 tx.End() 407 span2, ctx := apm.StartSpanOptions(ctx, "whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(20 * time.Millisecond)}) 408 span1.Duration = 20 * time.Millisecond // t0 + 30ms 409 span2.Duration = 10 * time.Millisecond // t0 + 30ms 410 span1.End() 411 span2.End() 412 413 tracer.Flush(nil) 414 tracer.SendMetrics(nil) 415 416 assertBreakdownMetrics(t, []model.Metrics{ 417 // The db.mysql span should not be included in breakdown, 418 // as it started after the transaction ended. The explicit 419 // "app" span should not be included in the self_time value, 420 // it should only have been used for subtracting from the 421 // transaction's duration. 422 spanSelfTimeMetrics("test", "request", "app", "", 1, 10*time.Millisecond), 423 }, payloadsBreakdownMetrics(transport)) 424 } 425 426 func TestBreakdownMetrics_AcceptanceTest10(t *testing.T) { 427 // total self type 428 // ██████████░░░░░░░░░░ 20 10 transaction 429 // └─────────████████████████████ 20 20 db.mysql 430 // 10 20 30 431 432 tracer, transport := transporttest.NewRecorderTracer() 433 defer tracer.Close() 434 435 t0 := time.Now() 436 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 437 span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)}) 438 tx.Duration = 20 * time.Millisecond // t0 + 20ms 439 tx.End() 440 span.Duration = 20 * time.Millisecond // t0 + 30ms 441 span.End() 442 443 tracer.Flush(nil) 444 tracer.SendMetrics(nil) 445 446 assertBreakdownMetrics(t, []model.Metrics{ 447 // The db.mysql span should not be included in breakdown, 448 // as it ended after the transaction ended. 449 spanSelfTimeMetrics("test", "request", "app", "", 1, 10*time.Millisecond), 450 }, payloadsBreakdownMetrics(transport)) 451 } 452 453 func TestBreakdownMetrics_AcceptanceTest11(t *testing.T) { 454 // total self type 455 // ██████████ 10 10 transaction 456 // └───────────────────██████████ 10 10 db.mysql 457 // 10 20 30 458 459 tracer, transport := transporttest.NewRecorderTracer() 460 defer tracer.Close() 461 462 t0 := time.Now() 463 tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0}) 464 tx.Duration = 10 * time.Millisecond // t0 + 10ms 465 tx.End() 466 span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(20 * time.Millisecond)}) 467 span.Duration = 10 * time.Millisecond // t0 + 30ms 468 span.End() 469 470 tracer.Flush(nil) 471 tracer.SendMetrics(nil) 472 473 assertBreakdownMetrics(t, []model.Metrics{ 474 // The db.mysql span should not be included in breakdown, 475 // as it started and ended after the transaction ended. 476 spanSelfTimeMetrics("test", "request", "app", "", 1, 10*time.Millisecond), 477 }, payloadsBreakdownMetrics(transport)) 478 } 479 480 func spanSelfTimeMetrics(txName, txType, spanType, spanSubtype string, count int, sum time.Duration) model.Metrics { 481 return model.Metrics{ 482 Transaction: model.MetricsTransaction{ 483 Type: txType, 484 Name: txName, 485 }, 486 Span: model.MetricsSpan{ 487 Type: spanType, 488 Subtype: spanSubtype, 489 }, 490 Samples: map[string]model.Metric{ 491 "span.self_time.count": {Value: float64(count)}, 492 "span.self_time.sum.us": {Value: float64(sum) / 1e3}, 493 }, 494 } 495 } 496 497 func assertBreakdownMetrics(t *testing.T, expect []model.Metrics, metrics []model.Metrics) { 498 for i := range metrics { 499 metrics[i].Timestamp = model.Time{} 500 } 501 assert.ElementsMatch(t, expect, metrics) 502 } 503 504 func payloadsBreakdownMetrics(t *transporttest.RecorderTransport) []model.Metrics { 505 p := t.Payloads() 506 ms := make([]model.Metrics, 0, len(p.Metrics)) 507 for _, m := range p.Metrics { 508 if m.Transaction.Type == "" { 509 continue 510 } 511 ms = append(ms, m) 512 } 513 return ms 514 }