github.com/prebid/prebid-server/v2@v2.18.0/metrics/prometheus/prometheus_test.go (about) 1 package prometheusmetrics 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "github.com/prebid/prebid-server/v2/config" 9 "github.com/prebid/prebid-server/v2/metrics" 10 "github.com/prebid/prebid-server/v2/openrtb_ext" 11 "github.com/prometheus/client_golang/prometheus" 12 dto "github.com/prometheus/client_model/go" 13 "github.com/stretchr/testify/assert" 14 ) 15 16 var modulesStages = map[string][]string{"foobar": {"entry", "raw"}, "another_module": {"raw", "auction"}} 17 18 func createMetricsForTesting() *Metrics { 19 syncerKeys := []string{} 20 return NewMetrics(config.PrometheusMetrics{ 21 Port: 8080, 22 Namespace: "prebid", 23 Subsystem: "server", 24 }, config.DisabledMetrics{}, syncerKeys, modulesStages) 25 } 26 27 func TestMetricCountGatekeeping(t *testing.T) { 28 m := createMetricsForTesting() 29 30 // Gather All Metrics 31 metricFamilies, err := m.Gatherer.Gather() 32 assert.NoError(t, err, "gather metics") 33 34 // Summarize By Adapter Cardinality 35 // - This requires metrics to be preloaded. We don't preload account metrics, so we can't test those. 36 generalCardinalityCount := 0 37 adapterCardinalityCount := 0 38 for _, metricFamily := range metricFamilies { 39 for _, metric := range metricFamily.GetMetric() { 40 isPerAdapter := false 41 for _, label := range metric.GetLabel() { 42 if label.GetName() == adapterLabel { 43 isPerAdapter = true 44 } 45 } 46 47 if isPerAdapter { 48 adapterCardinalityCount++ 49 } else { 50 generalCardinalityCount++ 51 } 52 } 53 } 54 55 // Calculate Per-Adapter Cardinality 56 adapterCount := len(openrtb_ext.CoreBidderNames()) 57 perAdapterCardinalityCount := adapterCardinalityCount / adapterCount 58 // Verify General Cardinality 59 // - This assertion provides a warning for newly added high-cardinality non-adapter specific metrics. The hardcoded limit 60 // is an arbitrary soft ceiling. Thought should be given as to the value of the new metrics if you find yourself 61 // needing to increase this number. 62 assert.True(t, generalCardinalityCount <= 500, "General Cardinality") 63 64 // Verify Per-Adapter Cardinality 65 // - This assertion provides a warning for newly added adapter metrics. Threre are 40+ adapters which makes the 66 // cost of new per-adapter metrics rather expensive. Thought should be given when adding new per-adapter metrics. 67 assert.True(t, perAdapterCardinalityCount <= 31, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount) 68 } 69 70 func TestConnectionMetrics(t *testing.T) { 71 testCases := []struct { 72 description string 73 testCase func(m *Metrics) 74 expectedOpenedCount float64 75 expectedOpenedErrorCount float64 76 expectedClosedCount float64 77 expectedClosedErrorCount float64 78 }{ 79 { 80 description: "Open Success", 81 testCase: func(m *Metrics) { 82 m.RecordConnectionAccept(true) 83 }, 84 expectedOpenedCount: 1, 85 expectedOpenedErrorCount: 0, 86 expectedClosedCount: 0, 87 expectedClosedErrorCount: 0, 88 }, 89 { 90 description: "Open Error", 91 testCase: func(m *Metrics) { 92 m.RecordConnectionAccept(false) 93 }, 94 expectedOpenedCount: 0, 95 expectedOpenedErrorCount: 1, 96 expectedClosedCount: 0, 97 expectedClosedErrorCount: 0, 98 }, 99 { 100 description: "Closed Success", 101 testCase: func(m *Metrics) { 102 m.RecordConnectionClose(true) 103 }, 104 expectedOpenedCount: 0, 105 expectedOpenedErrorCount: 0, 106 expectedClosedCount: 1, 107 expectedClosedErrorCount: 0, 108 }, 109 { 110 description: "Closed Error", 111 testCase: func(m *Metrics) { 112 m.RecordConnectionClose(false) 113 }, 114 expectedOpenedCount: 0, 115 expectedOpenedErrorCount: 0, 116 expectedClosedCount: 0, 117 expectedClosedErrorCount: 1, 118 }, 119 } 120 121 for _, test := range testCases { 122 m := createMetricsForTesting() 123 124 test.testCase(m) 125 126 assertCounterValue(t, test.description, "connectionsClosed", m.connectionsClosed, 127 test.expectedClosedCount) 128 assertCounterValue(t, test.description, "connectionsOpened", m.connectionsOpened, 129 test.expectedOpenedCount) 130 assertCounterVecValue(t, test.description, "connectionsError[type=accept]", m.connectionsError, 131 test.expectedOpenedErrorCount, prometheus.Labels{ 132 connectionErrorLabel: connectionAcceptError, 133 }) 134 assertCounterVecValue(t, test.description, "connectionsError[type=close]", m.connectionsError, 135 test.expectedClosedErrorCount, prometheus.Labels{ 136 connectionErrorLabel: connectionCloseError, 137 }) 138 } 139 } 140 141 func TestRequestMetric(t *testing.T) { 142 m := createMetricsForTesting() 143 requestType := metrics.ReqTypeORTB2Web 144 requestStatus := metrics.RequestStatusBlacklisted 145 146 m.RecordRequest(metrics.Labels{ 147 RType: requestType, 148 RequestStatus: requestStatus, 149 }) 150 151 expectedCount := float64(1) 152 assertCounterVecValue(t, "", "requests", m.requests, 153 expectedCount, 154 prometheus.Labels{ 155 requestTypeLabel: string(requestType), 156 requestStatusLabel: string(requestStatus), 157 }) 158 } 159 160 func TestDebugRequestMetric(t *testing.T) { 161 testCases := []struct { 162 description string 163 givenDebugEnabledFlag bool 164 givenAccountDebugMetricsDisabled bool 165 expectedAccountDebugCount float64 166 expectedDebugCount float64 167 }{ 168 { 169 description: "Debug is enabled and account debug is enabled, both metrics should be updated", 170 givenDebugEnabledFlag: true, 171 givenAccountDebugMetricsDisabled: false, 172 expectedDebugCount: 1, 173 expectedAccountDebugCount: 1, 174 }, 175 { 176 description: "Debug and account debug are disabled, niether metrics should be updated", 177 givenDebugEnabledFlag: false, 178 givenAccountDebugMetricsDisabled: true, 179 expectedDebugCount: 0, 180 expectedAccountDebugCount: 0, 181 }, 182 { 183 description: "Debug is enabled but account debug is disabled, only non-account debug count should increment", 184 givenDebugEnabledFlag: true, 185 givenAccountDebugMetricsDisabled: true, 186 expectedDebugCount: 1, 187 expectedAccountDebugCount: 0, 188 }, 189 { 190 description: "Debug is disabled and account debug is enabled, niether metrics should increment", 191 givenDebugEnabledFlag: false, 192 givenAccountDebugMetricsDisabled: false, 193 expectedDebugCount: 0, 194 expectedAccountDebugCount: 0, 195 }, 196 } 197 198 for _, test := range testCases { 199 m := createMetricsForTesting() 200 m.metricsDisabled.AccountDebug = test.givenAccountDebugMetricsDisabled 201 m.RecordDebugRequest(test.givenDebugEnabledFlag, "acct-id") 202 203 assertCounterVecValue(t, "", "account debug requests", m.accountDebugRequests, test.expectedAccountDebugCount, prometheus.Labels{accountLabel: "acct-id"}) 204 assertCounterValue(t, "", "debug requests", m.debugRequests, test.expectedDebugCount) 205 } 206 } 207 208 func TestBidValidationCreativeSizeMetric(t *testing.T) { 209 testCases := []struct { 210 description string 211 givenDebugEnabledFlag bool 212 givenAccountAdapterMetricsDisabled bool 213 expectedAdapterCount float64 214 expectedAccountCount float64 215 }{ 216 { 217 description: "Account Metric isn't disabled, so both metrics should be incremented", 218 givenAccountAdapterMetricsDisabled: false, 219 expectedAdapterCount: 1, 220 expectedAccountCount: 1, 221 }, 222 { 223 description: "Account Metric is disabled, so only adapter metric should be incremented", 224 givenAccountAdapterMetricsDisabled: true, 225 expectedAdapterCount: 1, 226 expectedAccountCount: 0, 227 }, 228 } 229 adapterName := openrtb_ext.BidderName("AnyName") 230 lowerCasedAdapterName := "anyname" 231 for _, test := range testCases { 232 m := createMetricsForTesting() 233 m.metricsDisabled.AccountAdapterDetails = test.givenAccountAdapterMetricsDisabled 234 m.RecordBidValidationCreativeSizeError(adapterName, "acct-id") 235 m.RecordBidValidationCreativeSizeWarn(adapterName, "acct-id") 236 237 assertCounterVecValue(t, "", "account bid validation", m.accountBidResponseValidationSizeError, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) 238 assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) 239 240 assertCounterVecValue(t, "", "account bid validation", m.accountBidResponseValidationSizeWarn, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) 241 assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) 242 } 243 } 244 245 func TestBidValidationSecureMarkupMetric(t *testing.T) { 246 testCases := []struct { 247 description string 248 givenDebugEnabledFlag bool 249 givenAccountAdapterMetricsDisabled bool 250 expectedAdapterCount float64 251 expectedAccountCount float64 252 }{ 253 { 254 description: "Account Metric isn't disabled, so both metrics should be incremented", 255 givenAccountAdapterMetricsDisabled: false, 256 expectedAdapterCount: 1, 257 expectedAccountCount: 1, 258 }, 259 { 260 description: "Account Metric is disabled, so only adapter metric should be incremented", 261 givenAccountAdapterMetricsDisabled: true, 262 expectedAdapterCount: 1, 263 expectedAccountCount: 0, 264 }, 265 } 266 267 adapterName := openrtb_ext.BidderName("AnyName") 268 lowerCasedAdapterName := "anyname" 269 for _, test := range testCases { 270 m := createMetricsForTesting() 271 m.metricsDisabled.AccountAdapterDetails = test.givenAccountAdapterMetricsDisabled 272 m.RecordBidValidationSecureMarkupError(adapterName, "acct-id") 273 m.RecordBidValidationSecureMarkupWarn(adapterName, "acct-id") 274 275 assertCounterVecValue(t, "", "Account Secure Markup Error", m.accountBidResponseSecureMarkupError, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) 276 assertCounterVecValue(t, "", "Adapter Secure Markup Error", m.adapterBidResponseSecureMarkupError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) 277 278 assertCounterVecValue(t, "", "Account Secure Markup Warn", m.accountBidResponseSecureMarkupWarn, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) 279 assertCounterVecValue(t, "", "Adapter Secure Markup Warn", m.adapterBidResponseSecureMarkupWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) 280 } 281 } 282 283 func TestRequestMetricWithoutCookie(t *testing.T) { 284 requestType := metrics.ReqTypeORTB2Web 285 performTest := func(m *Metrics, cookieFlag metrics.CookieFlag) { 286 m.RecordRequest(metrics.Labels{ 287 RType: requestType, 288 RequestStatus: metrics.RequestStatusBlacklisted, 289 CookieFlag: cookieFlag, 290 }) 291 } 292 293 testCases := []struct { 294 description string 295 testCase func(m *Metrics) 296 cookieFlag metrics.CookieFlag 297 expectedCount float64 298 }{ 299 { 300 description: "Yes", 301 testCase: func(m *Metrics) { 302 performTest(m, metrics.CookieFlagYes) 303 }, 304 expectedCount: 0, 305 }, 306 { 307 description: "No", 308 testCase: func(m *Metrics) { 309 performTest(m, metrics.CookieFlagNo) 310 }, 311 expectedCount: 1, 312 }, 313 { 314 description: "Unknown", 315 testCase: func(m *Metrics) { 316 performTest(m, metrics.CookieFlagUnknown) 317 }, 318 expectedCount: 0, 319 }, 320 } 321 322 for _, test := range testCases { 323 m := createMetricsForTesting() 324 325 test.testCase(m) 326 327 assertCounterVecValue(t, test.description, "requestsWithoutCookie", m.requestsWithoutCookie, 328 test.expectedCount, 329 prometheus.Labels{ 330 requestTypeLabel: string(requestType), 331 }) 332 } 333 } 334 335 func TestAccountMetric(t *testing.T) { 336 knownPubID := "knownPublisher" 337 performTest := func(m *Metrics, pubID string) { 338 m.RecordRequest(metrics.Labels{ 339 RType: metrics.ReqTypeORTB2Web, 340 RequestStatus: metrics.RequestStatusBlacklisted, 341 PubID: pubID, 342 }) 343 } 344 345 testCases := []struct { 346 description string 347 testCase func(m *Metrics) 348 expectedCount float64 349 }{ 350 { 351 description: "Known", 352 testCase: func(m *Metrics) { 353 performTest(m, knownPubID) 354 }, 355 expectedCount: 1, 356 }, 357 { 358 description: "Unknown", 359 testCase: func(m *Metrics) { 360 performTest(m, metrics.PublisherUnknown) 361 }, 362 expectedCount: 0, 363 }, 364 } 365 366 for _, test := range testCases { 367 m := createMetricsForTesting() 368 369 test.testCase(m) 370 371 assertCounterVecValue(t, test.description, "accountRequests", m.accountRequests, 372 test.expectedCount, 373 prometheus.Labels{ 374 accountLabel: knownPubID, 375 }) 376 } 377 } 378 379 func TestImpressionsMetric(t *testing.T) { 380 performTest := func(m *Metrics, isBanner, isVideo, isAudio, isNative bool) { 381 m.RecordImps(metrics.ImpLabels{ 382 BannerImps: isBanner, 383 VideoImps: isVideo, 384 AudioImps: isAudio, 385 NativeImps: isNative, 386 }) 387 } 388 389 testCases := []struct { 390 description string 391 testCase func(m *Metrics) 392 expectedBannerCount float64 393 expectedVideoCount float64 394 expectedAudioCount float64 395 expectedNativeCount float64 396 }{ 397 { 398 description: "Banner Only", 399 testCase: func(m *Metrics) { 400 performTest(m, true, false, false, false) 401 }, 402 expectedBannerCount: 1, 403 expectedVideoCount: 0, 404 expectedAudioCount: 0, 405 expectedNativeCount: 0, 406 }, 407 { 408 description: "Video Only", 409 testCase: func(m *Metrics) { 410 performTest(m, false, true, false, false) 411 }, 412 expectedBannerCount: 0, 413 expectedVideoCount: 1, 414 expectedAudioCount: 0, 415 expectedNativeCount: 0, 416 }, 417 { 418 description: "Audio Only", 419 testCase: func(m *Metrics) { 420 performTest(m, false, false, true, false) 421 }, 422 expectedBannerCount: 0, 423 expectedVideoCount: 0, 424 expectedAudioCount: 1, 425 expectedNativeCount: 0, 426 }, 427 { 428 description: "Native Only", 429 testCase: func(m *Metrics) { 430 performTest(m, false, false, false, true) 431 }, 432 expectedBannerCount: 0, 433 expectedVideoCount: 0, 434 expectedAudioCount: 0, 435 expectedNativeCount: 1, 436 }, 437 { 438 description: "Multiple Types", 439 testCase: func(m *Metrics) { 440 performTest(m, true, false, false, true) 441 }, 442 expectedBannerCount: 1, 443 expectedVideoCount: 0, 444 expectedAudioCount: 0, 445 expectedNativeCount: 1, 446 }, 447 } 448 449 for _, test := range testCases { 450 m := createMetricsForTesting() 451 452 test.testCase(m) 453 454 var bannerCount float64 455 var videoCount float64 456 var audioCount float64 457 var nativeCount float64 458 processMetrics(m.impressions, func(m dto.Metric) { 459 value := m.GetCounter().GetValue() 460 for _, label := range m.GetLabel() { 461 if label.GetValue() == "true" { 462 switch label.GetName() { 463 case isBannerLabel: 464 bannerCount += value 465 case isVideoLabel: 466 videoCount += value 467 case isAudioLabel: 468 audioCount += value 469 case isNativeLabel: 470 nativeCount += value 471 } 472 } 473 } 474 }) 475 assert.Equal(t, test.expectedBannerCount, bannerCount, test.description+":banner") 476 assert.Equal(t, test.expectedVideoCount, videoCount, test.description+":video") 477 assert.Equal(t, test.expectedAudioCount, audioCount, test.description+":audio") 478 assert.Equal(t, test.expectedNativeCount, nativeCount, test.description+":native") 479 } 480 } 481 482 func TestRequestTimeMetric(t *testing.T) { 483 requestType := metrics.ReqTypeORTB2Web 484 performTest := func(m *Metrics, requestStatus metrics.RequestStatus, timeInMs float64) { 485 m.RecordRequestTime(metrics.Labels{ 486 RType: requestType, 487 RequestStatus: requestStatus, 488 }, time.Duration(timeInMs)*time.Millisecond) 489 } 490 491 testCases := []struct { 492 description string 493 testCase func(m *Metrics) 494 expectedCount uint64 495 expectedSum float64 496 }{ 497 { 498 description: "Success", 499 testCase: func(m *Metrics) { 500 performTest(m, metrics.RequestStatusOK, 500) 501 }, 502 expectedCount: 1, 503 expectedSum: 0.5, 504 }, 505 { 506 description: "Error", 507 testCase: func(m *Metrics) { 508 performTest(m, metrics.RequestStatusErr, 500) 509 }, 510 expectedCount: 0, 511 expectedSum: 0, 512 }, 513 } 514 515 for _, test := range testCases { 516 m := createMetricsForTesting() 517 518 test.testCase(m) 519 520 result := getHistogramFromHistogramVec(m.requestsTimer, requestTypeLabel, string(requestType)) 521 assertHistogram(t, test.description, result, test.expectedCount, test.expectedSum) 522 } 523 } 524 525 func TestRecordOverheadTimeMetric(t *testing.T) { 526 testCases := []struct { 527 description string 528 overheadType metrics.OverheadType 529 timeInMs float64 530 expectedCount uint64 531 expectedSum float64 532 }{ 533 { 534 description: "record-pre-bidder-overhead-time-1", 535 overheadType: metrics.PreBidder, 536 timeInMs: 500, 537 expectedCount: 1, 538 expectedSum: 0.5, 539 }, 540 { 541 description: "record-pre-bidder-overhead-time-2", 542 overheadType: metrics.PreBidder, 543 timeInMs: 400, 544 expectedCount: 2, 545 expectedSum: 0.9, 546 }, 547 { 548 description: "record-auction-response-overhead-time", 549 overheadType: metrics.MakeAuctionResponse, 550 timeInMs: 500, 551 expectedCount: 1, 552 expectedSum: 0.5, 553 }, 554 { 555 description: "record-make-bidder-requests-overhead-time", 556 overheadType: metrics.MakeBidderRequests, 557 timeInMs: 500, 558 expectedCount: 1, 559 expectedSum: 0.5, 560 }, 561 } 562 563 metric := createMetricsForTesting() 564 for _, test := range testCases { 565 metric.RecordOverheadTime(test.overheadType, time.Duration(test.timeInMs)*time.Millisecond) 566 resultingHistogram := getHistogramFromHistogramVec(metric.overheadTimer, overheadTypeLabel, test.overheadType.String()) 567 assertHistogram(t, test.description, resultingHistogram, test.expectedCount, test.expectedSum) 568 } 569 } 570 571 func TestRecordStoredDataFetchTime(t *testing.T) { 572 tests := []struct { 573 description string 574 dataType metrics.StoredDataType 575 fetchType metrics.StoredDataFetchType 576 }{ 577 { 578 description: "Update stored account histogram with all label", 579 dataType: metrics.AccountDataType, 580 fetchType: metrics.FetchAll, 581 }, 582 { 583 description: "Update stored AMP histogram with all label", 584 dataType: metrics.AMPDataType, 585 fetchType: metrics.FetchAll, 586 }, 587 { 588 description: "Update stored category histogram with all label", 589 dataType: metrics.CategoryDataType, 590 fetchType: metrics.FetchAll, 591 }, 592 { 593 description: "Update stored request histogram with all label", 594 dataType: metrics.RequestDataType, 595 fetchType: metrics.FetchAll, 596 }, 597 { 598 description: "Update stored video histogram with all label", 599 dataType: metrics.VideoDataType, 600 fetchType: metrics.FetchAll, 601 }, 602 { 603 description: "Update stored account histogram with delta label", 604 dataType: metrics.AccountDataType, 605 fetchType: metrics.FetchDelta, 606 }, 607 { 608 description: "Update stored AMP histogram with delta label", 609 dataType: metrics.AMPDataType, 610 fetchType: metrics.FetchDelta, 611 }, 612 { 613 description: "Update stored category histogram with delta label", 614 dataType: metrics.CategoryDataType, 615 fetchType: metrics.FetchDelta, 616 }, 617 { 618 description: "Update stored request histogram with delta label", 619 dataType: metrics.RequestDataType, 620 fetchType: metrics.FetchDelta, 621 }, 622 { 623 description: "Update stored video histogram with delta label", 624 dataType: metrics.VideoDataType, 625 fetchType: metrics.FetchDelta, 626 }, 627 { 628 description: "Update stored responses histogram with delta label", 629 dataType: metrics.ResponseDataType, 630 fetchType: metrics.FetchDelta, 631 }, 632 } 633 634 for _, tt := range tests { 635 m := createMetricsForTesting() 636 637 fetchTime := time.Duration(0.5 * float64(time.Second)) 638 m.RecordStoredDataFetchTime(metrics.StoredDataLabels{ 639 DataType: tt.dataType, 640 DataFetchType: tt.fetchType, 641 }, fetchTime) 642 643 var metricsTimer *prometheus.HistogramVec 644 switch tt.dataType { 645 case metrics.AccountDataType: 646 metricsTimer = m.storedAccountFetchTimer 647 case metrics.AMPDataType: 648 metricsTimer = m.storedAMPFetchTimer 649 case metrics.CategoryDataType: 650 metricsTimer = m.storedCategoryFetchTimer 651 case metrics.RequestDataType: 652 metricsTimer = m.storedRequestFetchTimer 653 case metrics.VideoDataType: 654 metricsTimer = m.storedVideoFetchTimer 655 case metrics.ResponseDataType: 656 metricsTimer = m.storedResponsesFetchTimer 657 } 658 659 result := getHistogramFromHistogramVec( 660 metricsTimer, 661 storedDataFetchTypeLabel, 662 string(tt.fetchType)) 663 assertHistogram(t, tt.description, result, 1, 0.5) 664 } 665 } 666 667 func TestRecordStoredDataError(t *testing.T) { 668 tests := []struct { 669 description string 670 dataType metrics.StoredDataType 671 errorType metrics.StoredDataError 672 metricName string 673 }{ 674 { 675 description: "Update stored_account_errors counter with network label", 676 dataType: metrics.AccountDataType, 677 errorType: metrics.StoredDataErrorNetwork, 678 metricName: "stored_account_errors", 679 }, 680 { 681 description: "Update stored_amp_errors counter with network label", 682 dataType: metrics.AMPDataType, 683 errorType: metrics.StoredDataErrorNetwork, 684 metricName: "stored_amp_errors", 685 }, 686 { 687 description: "Update stored_category_errors counter with network label", 688 dataType: metrics.CategoryDataType, 689 errorType: metrics.StoredDataErrorNetwork, 690 metricName: "stored_category_errors", 691 }, 692 { 693 description: "Update stored_request_errors counter with network label", 694 dataType: metrics.RequestDataType, 695 errorType: metrics.StoredDataErrorNetwork, 696 metricName: "stored_request_errors", 697 }, 698 { 699 description: "Update stored_video_errors counter with network label", 700 dataType: metrics.VideoDataType, 701 errorType: metrics.StoredDataErrorNetwork, 702 metricName: "stored_video_errors", 703 }, 704 { 705 description: "Update stored_account_errors counter with undefined label", 706 dataType: metrics.AccountDataType, 707 errorType: metrics.StoredDataErrorUndefined, 708 metricName: "stored_account_errors", 709 }, 710 { 711 description: "Update stored_amp_errors counter with undefined label", 712 dataType: metrics.AMPDataType, 713 errorType: metrics.StoredDataErrorUndefined, 714 metricName: "stored_amp_errors", 715 }, 716 { 717 description: "Update stored_category_errors counter with undefined label", 718 dataType: metrics.CategoryDataType, 719 errorType: metrics.StoredDataErrorUndefined, 720 metricName: "stored_category_errors", 721 }, 722 { 723 description: "Update stored_request_errors counter with undefined label", 724 dataType: metrics.RequestDataType, 725 errorType: metrics.StoredDataErrorUndefined, 726 metricName: "stored_request_errors", 727 }, 728 { 729 description: "Update stored_video_errors counter with undefined label", 730 dataType: metrics.VideoDataType, 731 errorType: metrics.StoredDataErrorUndefined, 732 metricName: "stored_video_errors", 733 }, 734 { 735 description: "Update stored_response_errors counter with network label", 736 dataType: metrics.ResponseDataType, 737 errorType: metrics.StoredDataErrorNetwork, 738 metricName: "stored_response_errors", 739 }, 740 } 741 742 for _, tt := range tests { 743 m := createMetricsForTesting() 744 m.RecordStoredDataError(metrics.StoredDataLabels{ 745 DataType: tt.dataType, 746 Error: tt.errorType, 747 }) 748 749 var metricsCounter *prometheus.CounterVec 750 switch tt.dataType { 751 case metrics.AccountDataType: 752 metricsCounter = m.storedAccountErrors 753 case metrics.AMPDataType: 754 metricsCounter = m.storedAMPErrors 755 case metrics.CategoryDataType: 756 metricsCounter = m.storedCategoryErrors 757 case metrics.RequestDataType: 758 metricsCounter = m.storedRequestErrors 759 case metrics.VideoDataType: 760 metricsCounter = m.storedVideoErrors 761 case metrics.ResponseDataType: 762 metricsCounter = m.storedResponsesErrors 763 } 764 765 assertCounterVecValue(t, tt.description, tt.metricName, metricsCounter, 766 1, 767 prometheus.Labels{ 768 storedDataErrorLabel: string(tt.errorType), 769 }) 770 } 771 } 772 773 func TestAdapterBidReceivedMetric(t *testing.T) { 774 adapterName := openrtb_ext.BidderName("anyName") 775 lowerCasedAdapterName := "anyname" 776 performTest := func(m *Metrics, hasAdm bool) { 777 labels := metrics.AdapterLabels{ 778 Adapter: adapterName, 779 } 780 bidType := openrtb_ext.BidTypeBanner 781 m.RecordAdapterBidReceived(labels, bidType, hasAdm) 782 } 783 784 testCases := []struct { 785 description string 786 testCase func(m *Metrics) 787 expectedAdmCount float64 788 expectedNurlCount float64 789 }{ 790 { 791 description: "AdM", 792 testCase: func(m *Metrics) { 793 performTest(m, true) 794 }, 795 expectedAdmCount: 1, 796 expectedNurlCount: 0, 797 }, 798 { 799 description: "Nurl", 800 testCase: func(m *Metrics) { 801 performTest(m, false) 802 }, 803 expectedAdmCount: 0, 804 expectedNurlCount: 1, 805 }, 806 } 807 808 for _, test := range testCases { 809 m := createMetricsForTesting() 810 811 test.testCase(m) 812 813 assertCounterVecValue(t, test.description, "adapterBids[adm]", m.adapterBids, 814 test.expectedAdmCount, 815 prometheus.Labels{ 816 adapterLabel: lowerCasedAdapterName, 817 markupDeliveryLabel: markupDeliveryAdm, 818 }) 819 assertCounterVecValue(t, test.description, "adapterBids[nurl]", m.adapterBids, 820 test.expectedNurlCount, 821 prometheus.Labels{ 822 adapterLabel: lowerCasedAdapterName, 823 markupDeliveryLabel: markupDeliveryNurl, 824 }) 825 } 826 } 827 828 func TestRecordAdapterPriceMetric(t *testing.T) { 829 m := createMetricsForTesting() 830 adapterName := "anyName" 831 lowerCasedAdapterName := "anyname" 832 cpm := float64(42) 833 834 m.RecordAdapterPrice(metrics.AdapterLabels{ 835 Adapter: openrtb_ext.BidderName(adapterName), 836 }, cpm) 837 838 expectedCount := uint64(1) 839 expectedSum := cpm 840 result := getHistogramFromHistogramVec(m.adapterPrices, adapterLabel, lowerCasedAdapterName) 841 assertHistogram(t, "adapterPrices", result, expectedCount, expectedSum) 842 } 843 844 func TestAdapterRequestMetrics(t *testing.T) { 845 adapterName := "anyName" 846 lowerCasedAdapterName := "anyname" 847 performTest := func(m *Metrics, cookieFlag metrics.CookieFlag, adapterBids metrics.AdapterBid) { 848 labels := metrics.AdapterLabels{ 849 Adapter: openrtb_ext.BidderName(adapterName), 850 CookieFlag: cookieFlag, 851 AdapterBids: adapterBids, 852 } 853 m.RecordAdapterRequest(labels) 854 } 855 856 testCases := []struct { 857 description string 858 testCase func(m *Metrics) 859 expectedCount float64 860 expectedCookieNoCount float64 861 expectedCookieYesCount float64 862 expectedCookieUnknownCount float64 863 expectedHasBidsCount float64 864 }{ 865 { 866 description: "No Cookie & No Bids", 867 testCase: func(m *Metrics) { 868 performTest(m, metrics.CookieFlagNo, metrics.AdapterBidNone) 869 }, 870 expectedCount: 1, 871 expectedCookieNoCount: 1, 872 expectedCookieYesCount: 0, 873 expectedCookieUnknownCount: 0, 874 expectedHasBidsCount: 0, 875 }, 876 { 877 description: "Unknown Cookie & No Bids", 878 testCase: func(m *Metrics) { 879 performTest(m, metrics.CookieFlagUnknown, metrics.AdapterBidNone) 880 }, 881 expectedCount: 1, 882 expectedCookieNoCount: 0, 883 expectedCookieYesCount: 0, 884 expectedCookieUnknownCount: 1, 885 expectedHasBidsCount: 0, 886 }, 887 { 888 description: "Has Cookie & No Bids", 889 testCase: func(m *Metrics) { 890 performTest(m, metrics.CookieFlagYes, metrics.AdapterBidNone) 891 }, 892 expectedCount: 1, 893 expectedCookieNoCount: 0, 894 expectedCookieYesCount: 1, 895 expectedCookieUnknownCount: 0, 896 expectedHasBidsCount: 0, 897 }, 898 { 899 description: "No Cookie & Bids Present", 900 testCase: func(m *Metrics) { 901 performTest(m, metrics.CookieFlagNo, metrics.AdapterBidPresent) 902 }, 903 expectedCount: 1, 904 expectedCookieNoCount: 1, 905 expectedCookieYesCount: 0, 906 expectedCookieUnknownCount: 0, 907 expectedHasBidsCount: 1, 908 }, 909 { 910 description: "Unknown Cookie & Bids Present", 911 testCase: func(m *Metrics) { 912 performTest(m, metrics.CookieFlagUnknown, metrics.AdapterBidPresent) 913 }, 914 expectedCount: 1, 915 expectedCookieNoCount: 0, 916 expectedCookieYesCount: 0, 917 expectedCookieUnknownCount: 1, 918 expectedHasBidsCount: 1, 919 }, 920 { 921 description: "Has Cookie & Bids Present", 922 testCase: func(m *Metrics) { 923 performTest(m, metrics.CookieFlagYes, metrics.AdapterBidPresent) 924 }, 925 expectedCount: 1, 926 expectedCookieNoCount: 0, 927 expectedCookieYesCount: 1, 928 expectedCookieUnknownCount: 0, 929 expectedHasBidsCount: 1, 930 }, 931 } 932 933 for _, test := range testCases { 934 m := createMetricsForTesting() 935 936 test.testCase(m) 937 938 var totalCount float64 939 var totalCookieNoCount float64 940 var totalCookieYesCount float64 941 var totalCookieUnknownCount float64 942 var totalHasBidsCount float64 943 processMetrics(m.adapterRequests, func(m dto.Metric) { 944 isMetricForAdapter := false 945 for _, label := range m.GetLabel() { 946 if label.GetName() == adapterLabel && label.GetValue() == lowerCasedAdapterName { 947 isMetricForAdapter = true 948 } 949 } 950 951 if isMetricForAdapter { 952 value := m.GetCounter().GetValue() 953 totalCount += value 954 for _, label := range m.GetLabel() { 955 956 if label.GetName() == hasBidsLabel && label.GetValue() == "true" { 957 totalHasBidsCount += value 958 } 959 960 if label.GetName() == cookieLabel { 961 switch label.GetValue() { 962 case string(metrics.CookieFlagNo): 963 totalCookieNoCount += value 964 case string(metrics.CookieFlagYes): 965 totalCookieYesCount += value 966 case string(metrics.CookieFlagUnknown): 967 totalCookieUnknownCount += value 968 } 969 } 970 } 971 } 972 }) 973 assert.Equal(t, test.expectedCount, totalCount, test.description+":total") 974 assert.Equal(t, test.expectedCookieNoCount, totalCookieNoCount, test.description+":cookie=no") 975 assert.Equal(t, test.expectedCookieYesCount, totalCookieYesCount, test.description+":cookie=yes") 976 assert.Equal(t, test.expectedCookieUnknownCount, totalCookieUnknownCount, test.description+":cookie=unknown") 977 assert.Equal(t, test.expectedHasBidsCount, totalHasBidsCount, test.description+":hasBids") 978 } 979 } 980 981 func TestAdapterRequestErrorMetrics(t *testing.T) { 982 adapterName := "anyName" 983 lowerCasedAdapterName := "anyname" 984 performTest := func(m *Metrics, adapterErrors map[metrics.AdapterError]struct{}) { 985 labels := metrics.AdapterLabels{ 986 Adapter: openrtb_ext.BidderName(adapterName), 987 AdapterErrors: adapterErrors, 988 CookieFlag: metrics.CookieFlagUnknown, 989 AdapterBids: metrics.AdapterBidPresent, 990 } 991 m.RecordAdapterRequest(labels) 992 } 993 994 testCases := []struct { 995 description string 996 testCase func(m *Metrics) 997 expectedErrorsCount float64 998 expectedBadInputErrorsCount float64 999 }{ 1000 { 1001 description: "No Errors", 1002 testCase: func(m *Metrics) { 1003 performTest(m, nil) 1004 }, 1005 expectedErrorsCount: 0, 1006 expectedBadInputErrorsCount: 0, 1007 }, 1008 { 1009 description: "Bad Input Error", 1010 testCase: func(m *Metrics) { 1011 performTest(m, map[metrics.AdapterError]struct{}{ 1012 metrics.AdapterErrorBadInput: {}, 1013 }) 1014 }, 1015 expectedErrorsCount: 1, 1016 expectedBadInputErrorsCount: 1, 1017 }, 1018 { 1019 description: "Other Error", 1020 testCase: func(m *Metrics) { 1021 performTest(m, map[metrics.AdapterError]struct{}{ 1022 metrics.AdapterErrorBadServerResponse: {}, 1023 }) 1024 }, 1025 expectedErrorsCount: 1, 1026 expectedBadInputErrorsCount: 0, 1027 }, 1028 } 1029 1030 for _, test := range testCases { 1031 m := createMetricsForTesting() 1032 1033 test.testCase(m) 1034 1035 var errorsCount float64 1036 var badInputErrorsCount float64 1037 processMetrics(m.adapterErrors, func(m dto.Metric) { 1038 isMetricForAdapter := false 1039 for _, label := range m.GetLabel() { 1040 if label.GetName() == adapterLabel && label.GetValue() == lowerCasedAdapterName { 1041 isMetricForAdapter = true 1042 } 1043 } 1044 1045 if isMetricForAdapter { 1046 value := m.GetCounter().GetValue() 1047 errorsCount += value 1048 for _, label := range m.GetLabel() { 1049 if label.GetName() == adapterErrorLabel && label.GetValue() == string(metrics.AdapterErrorBadInput) { 1050 badInputErrorsCount += value 1051 } 1052 } 1053 } 1054 }) 1055 assert.Equal(t, test.expectedErrorsCount, errorsCount, test.description+":errors") 1056 assert.Equal(t, test.expectedBadInputErrorsCount, badInputErrorsCount, test.description+":badInputErrors") 1057 } 1058 } 1059 1060 func TestAdapterTimeMetric(t *testing.T) { 1061 adapterName := "anyName" 1062 lowerCasedAdapterName := "anyname" 1063 performTest := func(m *Metrics, timeInMs float64, adapterErrors map[metrics.AdapterError]struct{}) { 1064 m.RecordAdapterTime(metrics.AdapterLabels{ 1065 Adapter: openrtb_ext.BidderName(adapterName), 1066 AdapterErrors: adapterErrors, 1067 }, time.Duration(timeInMs)*time.Millisecond) 1068 } 1069 1070 testCases := []struct { 1071 description string 1072 testCase func(m *Metrics) 1073 expectedCount uint64 1074 expectedSum float64 1075 }{ 1076 { 1077 description: "Success", 1078 testCase: func(m *Metrics) { 1079 performTest(m, 500, map[metrics.AdapterError]struct{}{}) 1080 }, 1081 expectedCount: 1, 1082 expectedSum: 0.5, 1083 }, 1084 { 1085 description: "Error", 1086 testCase: func(m *Metrics) { 1087 performTest(m, 500, map[metrics.AdapterError]struct{}{ 1088 metrics.AdapterErrorTimeout: {}, 1089 }) 1090 }, 1091 expectedCount: 0, 1092 expectedSum: 0, 1093 }, 1094 } 1095 1096 for _, test := range testCases { 1097 m := createMetricsForTesting() 1098 1099 test.testCase(m) 1100 1101 result := getHistogramFromHistogramVec(m.adapterRequestsTimer, adapterLabel, lowerCasedAdapterName) 1102 assertHistogram(t, test.description, result, test.expectedCount, test.expectedSum) 1103 } 1104 } 1105 1106 func TestAdapterPanicMetric(t *testing.T) { 1107 m := createMetricsForTesting() 1108 adapterName := openrtb_ext.BidderName("anyName") 1109 lowerCasedAdapterName := "anyname" 1110 m.RecordAdapterPanic(metrics.AdapterLabels{ 1111 Adapter: adapterName, 1112 }) 1113 1114 expectedCount := float64(1) 1115 assertCounterVecValue(t, "", "adapterPanics", m.adapterPanics, 1116 expectedCount, 1117 prometheus.Labels{ 1118 adapterLabel: lowerCasedAdapterName, 1119 }) 1120 } 1121 1122 func TestStoredReqCacheResultMetric(t *testing.T) { 1123 m := createMetricsForTesting() 1124 1125 hitCount := 42 1126 missCount := 108 1127 m.RecordStoredReqCacheResult(metrics.CacheHit, hitCount) 1128 m.RecordStoredReqCacheResult(metrics.CacheMiss, missCount) 1129 1130 assertCounterVecValue(t, "", "storedRequestCacheResult:hit", m.storedRequestCacheResult, 1131 float64(hitCount), 1132 prometheus.Labels{ 1133 cacheResultLabel: string(metrics.CacheHit), 1134 }) 1135 assertCounterVecValue(t, "", "storedRequestCacheResult:miss", m.storedRequestCacheResult, 1136 float64(missCount), 1137 prometheus.Labels{ 1138 cacheResultLabel: string(metrics.CacheMiss), 1139 }) 1140 } 1141 1142 func TestStoredImpCacheResultMetric(t *testing.T) { 1143 m := createMetricsForTesting() 1144 1145 hitCount := 41 1146 missCount := 107 1147 m.RecordStoredImpCacheResult(metrics.CacheHit, hitCount) 1148 m.RecordStoredImpCacheResult(metrics.CacheMiss, missCount) 1149 1150 assertCounterVecValue(t, "", "storedImpressionsCacheResult:hit", m.storedImpressionsCacheResult, 1151 float64(hitCount), 1152 prometheus.Labels{ 1153 cacheResultLabel: string(metrics.CacheHit), 1154 }) 1155 assertCounterVecValue(t, "", "storedImpressionsCacheResult:miss", m.storedImpressionsCacheResult, 1156 float64(missCount), 1157 prometheus.Labels{ 1158 cacheResultLabel: string(metrics.CacheMiss), 1159 }) 1160 } 1161 1162 func TestAccountCacheResultMetric(t *testing.T) { 1163 m := createMetricsForTesting() 1164 1165 hitCount := 37 1166 missCount := 92 1167 m.RecordAccountCacheResult(metrics.CacheHit, hitCount) 1168 m.RecordAccountCacheResult(metrics.CacheMiss, missCount) 1169 1170 assertCounterVecValue(t, "", "accountCacheResult:hit", m.accountCacheResult, 1171 float64(hitCount), 1172 prometheus.Labels{ 1173 cacheResultLabel: string(metrics.CacheHit), 1174 }) 1175 assertCounterVecValue(t, "", "accountCacheResult:miss", m.accountCacheResult, 1176 float64(missCount), 1177 prometheus.Labels{ 1178 cacheResultLabel: string(metrics.CacheMiss), 1179 }) 1180 } 1181 1182 func TestCookieSyncMetric(t *testing.T) { 1183 tests := []struct { 1184 status metrics.CookieSyncStatus 1185 label string 1186 }{ 1187 { 1188 status: metrics.CookieSyncOK, 1189 label: "ok", 1190 }, 1191 { 1192 status: metrics.CookieSyncBadRequest, 1193 label: "bad_request", 1194 }, 1195 { 1196 status: metrics.CookieSyncOptOut, 1197 label: "opt_out", 1198 }, 1199 { 1200 status: metrics.CookieSyncGDPRHostCookieBlocked, 1201 label: "gdpr_blocked_host_cookie", 1202 }, 1203 } 1204 1205 for _, test := range tests { 1206 m := createMetricsForTesting() 1207 1208 m.RecordCookieSync(test.status) 1209 1210 assertCounterVecValue(t, "", "cookie_sync_requests:"+test.label, m.cookieSync, 1211 float64(1), 1212 prometheus.Labels{ 1213 statusLabel: string(test.status), 1214 }) 1215 } 1216 } 1217 1218 func TestRecordSyncerRequestMetric(t *testing.T) { 1219 key := "anyKey" 1220 1221 tests := []struct { 1222 status metrics.SyncerCookieSyncStatus 1223 label string 1224 }{ 1225 { 1226 status: metrics.SyncerCookieSyncOK, 1227 label: "ok", 1228 }, 1229 { 1230 status: metrics.SyncerCookieSyncPrivacyBlocked, 1231 label: "privacy_blocked", 1232 }, 1233 { 1234 status: metrics.SyncerCookieSyncAlreadySynced, 1235 label: "already_synced", 1236 }, 1237 { 1238 status: metrics.SyncerCookieSyncTypeNotSupported, 1239 label: "type_not_supported", 1240 }, 1241 } 1242 1243 for _, test := range tests { 1244 m := createMetricsForTesting() 1245 1246 m.RecordSyncerRequest(key, test.status) 1247 1248 assertCounterVecValue(t, "", "syncer_requests:"+test.label, m.syncerRequests, 1249 float64(1), 1250 prometheus.Labels{ 1251 syncerLabel: key, 1252 statusLabel: string(test.status), 1253 }) 1254 } 1255 } 1256 1257 func TestSetUidMetric(t *testing.T) { 1258 tests := []struct { 1259 status metrics.SetUidStatus 1260 label string 1261 }{ 1262 { 1263 status: metrics.SetUidOK, 1264 label: "ok", 1265 }, 1266 { 1267 status: metrics.SetUidBadRequest, 1268 label: "bad_request", 1269 }, 1270 { 1271 status: metrics.SetUidOptOut, 1272 label: "opt_out", 1273 }, 1274 { 1275 status: metrics.SetUidGDPRHostCookieBlocked, 1276 label: "gdpr_blocked_host_cookie", 1277 }, 1278 { 1279 status: metrics.SetUidSyncerUnknown, 1280 label: "syncer_unknown", 1281 }, 1282 } 1283 1284 for _, test := range tests { 1285 m := createMetricsForTesting() 1286 1287 m.RecordSetUid(test.status) 1288 1289 assertCounterVecValue(t, "", "setuid_requests:"+test.label, m.setUid, 1290 float64(1), 1291 prometheus.Labels{ 1292 statusLabel: string(test.status), 1293 }) 1294 } 1295 } 1296 1297 func TestRecordSyncerSetMetric(t *testing.T) { 1298 key := "anyKey" 1299 1300 tests := []struct { 1301 status metrics.SyncerSetUidStatus 1302 label string 1303 }{ 1304 { 1305 status: metrics.SyncerSetUidOK, 1306 label: "ok", 1307 }, 1308 { 1309 status: metrics.SyncerSetUidCleared, 1310 label: "cleared", 1311 }, 1312 } 1313 1314 for _, test := range tests { 1315 m := createMetricsForTesting() 1316 1317 m.RecordSyncerSet(key, test.status) 1318 1319 assertCounterVecValue(t, "", "syncer_sets:"+test.label, m.syncerSets, 1320 float64(1), 1321 prometheus.Labels{ 1322 syncerLabel: key, 1323 statusLabel: string(test.status), 1324 }) 1325 } 1326 } 1327 1328 func TestPrebidCacheRequestTimeMetric(t *testing.T) { 1329 m := createMetricsForTesting() 1330 1331 m.RecordPrebidCacheRequestTime(true, time.Duration(100)*time.Millisecond) 1332 m.RecordPrebidCacheRequestTime(false, time.Duration(200)*time.Millisecond) 1333 1334 successExpectedCount := uint64(1) 1335 successExpectedSum := float64(0.1) 1336 successResult := getHistogramFromHistogramVec(m.prebidCacheWriteTimer, successLabel, "true") 1337 assertHistogram(t, "Success", successResult, successExpectedCount, successExpectedSum) 1338 1339 errorExpectedCount := uint64(1) 1340 errorExpectedSum := float64(0.2) 1341 errorResult := getHistogramFromHistogramVec(m.prebidCacheWriteTimer, successLabel, "false") 1342 assertHistogram(t, "Error", errorResult, errorExpectedCount, errorExpectedSum) 1343 } 1344 1345 func TestRecordRequestQueueTimeMetric(t *testing.T) { 1346 performTest := func(m *Metrics, requestStatus bool, requestType metrics.RequestType, timeInSec float64) { 1347 m.RecordRequestQueueTime(requestStatus, requestType, time.Duration(timeInSec*float64(time.Second))) 1348 } 1349 1350 testCases := []struct { 1351 description string 1352 status string 1353 testCase func(m *Metrics) 1354 expectedCount uint64 1355 expectedSum float64 1356 }{ 1357 { 1358 description: "Success", 1359 status: requestSuccessLabel, 1360 testCase: func(m *Metrics) { 1361 performTest(m, true, metrics.ReqTypeVideo, 2) 1362 }, 1363 expectedCount: 1, 1364 expectedSum: 2, 1365 }, 1366 { 1367 description: "TimeoutError", 1368 status: requestRejectLabel, 1369 testCase: func(m *Metrics) { 1370 performTest(m, false, metrics.ReqTypeVideo, 50) 1371 }, 1372 expectedCount: 1, 1373 expectedSum: 50, 1374 }, 1375 } 1376 1377 m := createMetricsForTesting() 1378 for _, test := range testCases { 1379 1380 test.testCase(m) 1381 1382 result := getHistogramFromHistogramVecByTwoKeys(m.requestsQueueTimer, requestTypeLabel, "video", requestStatusLabel, test.status) 1383 assertHistogram(t, test.description, result, test.expectedCount, test.expectedSum) 1384 } 1385 } 1386 1387 func TestTimeoutNotifications(t *testing.T) { 1388 m := createMetricsForTesting() 1389 1390 m.RecordTimeoutNotice(true) 1391 m.RecordTimeoutNotice(true) 1392 m.RecordTimeoutNotice(false) 1393 1394 assertCounterVecValue(t, "", "timeout_notifications:ok", m.timeoutNotifications, 1395 float64(2), 1396 prometheus.Labels{ 1397 successLabel: requestSuccessful, 1398 }) 1399 1400 assertCounterVecValue(t, "", "timeout_notifications:fail", m.timeoutNotifications, 1401 float64(1), 1402 prometheus.Labels{ 1403 successLabel: requestFailed, 1404 }) 1405 1406 } 1407 1408 func TestRecordDNSTime(t *testing.T) { 1409 type testIn struct { 1410 dnsLookupDuration time.Duration 1411 } 1412 type testOut struct { 1413 expDuration float64 1414 expCount uint64 1415 } 1416 testCases := []struct { 1417 description string 1418 in testIn 1419 out testOut 1420 }{ 1421 { 1422 description: "Five second DNS lookup time", 1423 in: testIn{ 1424 dnsLookupDuration: time.Second * 5, 1425 }, 1426 out: testOut{ 1427 expDuration: 5, 1428 expCount: 1, 1429 }, 1430 }, 1431 { 1432 description: "Zero DNS lookup time", 1433 in: testIn{}, 1434 out: testOut{ 1435 expDuration: 0, 1436 expCount: 1, 1437 }, 1438 }, 1439 } 1440 for i, test := range testCases { 1441 pm := createMetricsForTesting() 1442 pm.RecordDNSTime(test.in.dnsLookupDuration) 1443 1444 m := dto.Metric{} 1445 pm.dnsLookupTimer.Write(&m) 1446 histogram := *m.GetHistogram() 1447 1448 assert.Equal(t, test.out.expCount, histogram.GetSampleCount(), "[%d] Incorrect number of histogram entries. Desc: %s\n", i, test.description) 1449 assert.Equal(t, test.out.expDuration, histogram.GetSampleSum(), "[%d] Incorrect number of histogram cumulative values. Desc: %s\n", i, test.description) 1450 } 1451 } 1452 1453 func TestRecordTLSHandshakeTime(t *testing.T) { 1454 testCases := []struct { 1455 description string 1456 tLSHandshakeDuration time.Duration 1457 expectedDuration float64 1458 expectedCount uint64 1459 }{ 1460 { 1461 description: "Five second DNS lookup time", 1462 tLSHandshakeDuration: time.Second * 5, 1463 expectedDuration: 5, 1464 expectedCount: 1, 1465 }, 1466 { 1467 description: "Zero DNS lookup time", 1468 tLSHandshakeDuration: 0, 1469 expectedDuration: 0, 1470 expectedCount: 1, 1471 }, 1472 } 1473 for i, test := range testCases { 1474 pm := createMetricsForTesting() 1475 pm.RecordTLSHandshakeTime(test.tLSHandshakeDuration) 1476 1477 m := dto.Metric{} 1478 pm.tlsHandhakeTimer.Write(&m) 1479 histogram := *m.GetHistogram() 1480 1481 assert.Equal(t, test.expectedCount, histogram.GetSampleCount(), "[%d] Incorrect number of histogram entries. Desc: %s\n", i, test.description) 1482 assert.Equal(t, test.expectedDuration, histogram.GetSampleSum(), "[%d] Incorrect number of histogram cumulative values. Desc: %s\n", i, test.description) 1483 } 1484 } 1485 1486 func TestRecordBidderServerResponseTime(t *testing.T) { 1487 testCases := []struct { 1488 description string 1489 timeInMs float64 1490 expectedCount uint64 1491 expectedSum float64 1492 }{ 1493 { 1494 description: "record-bidder-server-response-time-1", 1495 timeInMs: 500, 1496 expectedCount: 1, 1497 expectedSum: 0.5, 1498 }, 1499 { 1500 description: "record-bidder-server-response-time-2", 1501 timeInMs: 400, 1502 expectedCount: 1, 1503 expectedSum: 0.4, 1504 }, 1505 } 1506 for _, test := range testCases { 1507 pm := createMetricsForTesting() 1508 pm.RecordBidderServerResponseTime(time.Duration(test.timeInMs) * time.Millisecond) 1509 1510 m := dto.Metric{} 1511 pm.bidderServerResponseTimer.Write(&m) 1512 histogram := *m.GetHistogram() 1513 1514 assert.Equal(t, test.expectedCount, histogram.GetSampleCount()) 1515 assert.Equal(t, test.expectedSum, histogram.GetSampleSum()) 1516 } 1517 } 1518 1519 func TestRecordAdapterConnections(t *testing.T) { 1520 adapterName := openrtb_ext.BidderName("Adapter") 1521 lowerCasedAdapterName := "adapter" 1522 1523 type testIn struct { 1524 adapterName openrtb_ext.BidderName 1525 connWasReused bool 1526 connWait time.Duration 1527 } 1528 1529 type testOut struct { 1530 expectedConnReusedCount int64 1531 expectedConnCreatedCount int64 1532 expectedConnWaitCount uint64 1533 expectedConnWaitTime float64 1534 } 1535 1536 testCases := []struct { 1537 description string 1538 in testIn 1539 out testOut 1540 }{ 1541 { 1542 description: "[1] Successful, new connection created, was idle, has connection wait", 1543 in: testIn{ 1544 adapterName: adapterName, 1545 connWasReused: false, 1546 connWait: time.Second * 5, 1547 }, 1548 out: testOut{ 1549 expectedConnReusedCount: 0, 1550 expectedConnCreatedCount: 1, 1551 expectedConnWaitCount: 1, 1552 expectedConnWaitTime: 5, 1553 }, 1554 }, 1555 { 1556 description: "[2] Successful, new connection created, not idle, has connection wait", 1557 in: testIn{ 1558 adapterName: adapterName, 1559 connWasReused: false, 1560 connWait: time.Second * 4, 1561 }, 1562 out: testOut{ 1563 expectedConnReusedCount: 0, 1564 expectedConnCreatedCount: 1, 1565 expectedConnWaitCount: 1, 1566 expectedConnWaitTime: 4, 1567 }, 1568 }, 1569 { 1570 description: "[3] Successful, was reused, was idle, no connection wait", 1571 in: testIn{ 1572 adapterName: adapterName, 1573 connWasReused: true, 1574 }, 1575 out: testOut{ 1576 expectedConnReusedCount: 1, 1577 expectedConnCreatedCount: 0, 1578 expectedConnWaitCount: 1, 1579 expectedConnWaitTime: 0, 1580 }, 1581 }, 1582 { 1583 description: "[4] Successful, was reused, not idle, has connection wait", 1584 in: testIn{ 1585 adapterName: adapterName, 1586 connWasReused: true, 1587 connWait: time.Second * 5, 1588 }, 1589 out: testOut{ 1590 expectedConnReusedCount: 1, 1591 expectedConnCreatedCount: 0, 1592 expectedConnWaitCount: 1, 1593 expectedConnWaitTime: 5, 1594 }, 1595 }, 1596 } 1597 1598 for i, test := range testCases { 1599 m := createMetricsForTesting() 1600 assertDesciptions := []string{ 1601 fmt.Sprintf("[%d] Metric: adapterReusedConnections; Desc: %s", i+1, test.description), 1602 fmt.Sprintf("[%d] Metric: adapterCreatedConnections; Desc: %s", i+1, test.description), 1603 fmt.Sprintf("[%d] Metric: adapterWaitConnectionCount; Desc: %s", i+1, test.description), 1604 fmt.Sprintf("[%d] Metric: adapterWaitConnectionTime; Desc: %s", i+1, test.description), 1605 } 1606 1607 m.RecordAdapterConnections(test.in.adapterName, test.in.connWasReused, test.in.connWait) 1608 1609 // Assert number of reused connections 1610 assertCounterVecValue(t, 1611 assertDesciptions[0], 1612 "adapter_connection_reused", 1613 m.adapterReusedConnections, 1614 float64(test.out.expectedConnReusedCount), 1615 prometheus.Labels{adapterLabel: lowerCasedAdapterName}) 1616 1617 // Assert number of new created connections 1618 assertCounterVecValue(t, 1619 assertDesciptions[1], 1620 "adapter_connection_created", 1621 m.adapterCreatedConnections, 1622 float64(test.out.expectedConnCreatedCount), 1623 prometheus.Labels{adapterLabel: lowerCasedAdapterName}) 1624 1625 // Assert connection wait time 1626 histogram := getHistogramFromHistogramVec(m.adapterConnectionWaitTime, adapterLabel, lowerCasedAdapterName) 1627 assert.Equal(t, test.out.expectedConnWaitCount, histogram.GetSampleCount(), assertDesciptions[2]) 1628 assert.Equal(t, test.out.expectedConnWaitTime, histogram.GetSampleSum(), assertDesciptions[3]) 1629 } 1630 } 1631 1632 func TestDisabledMetrics(t *testing.T) { 1633 prometheusMetrics := NewMetrics(config.PrometheusMetrics{ 1634 Port: 8080, 1635 Namespace: "prebid", 1636 Subsystem: "server", 1637 }, config.DisabledMetrics{ 1638 AdapterBuyerUIDScrubbed: true, 1639 AdapterConnectionMetrics: true, 1640 AdapterGDPRRequestBlocked: true, 1641 }, 1642 nil, nil) 1643 1644 // Assert counter vector was not initialized 1645 assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil") 1646 assert.Nil(t, prometheusMetrics.adapterScrubbedBuyerUIDs, "Counter Vector adapterScrubbedBuyerUIDs should be nil") 1647 assert.Nil(t, prometheusMetrics.adapterCreatedConnections, "Counter Vector adapterCreatedConnections should be nil") 1648 assert.Nil(t, prometheusMetrics.adapterConnectionWaitTime, "Counter Vector adapterConnectionWaitTime should be nil") 1649 assert.Nil(t, prometheusMetrics.adapterGDPRBlockedRequests, "Counter Vector adapterGDPRBlockedRequests should be nil") 1650 } 1651 1652 func TestRecordRequestPrivacy(t *testing.T) { 1653 m := createMetricsForTesting() 1654 1655 // CCPA 1656 m.RecordRequestPrivacy(metrics.PrivacyLabels{ 1657 CCPAEnforced: true, 1658 CCPAProvided: true, 1659 }) 1660 m.RecordRequestPrivacy(metrics.PrivacyLabels{ 1661 CCPAEnforced: true, 1662 CCPAProvided: false, 1663 }) 1664 m.RecordRequestPrivacy(metrics.PrivacyLabels{ 1665 CCPAEnforced: false, 1666 CCPAProvided: true, 1667 }) 1668 1669 // COPPA 1670 m.RecordRequestPrivacy(metrics.PrivacyLabels{ 1671 COPPAEnforced: true, 1672 }) 1673 1674 // LMT 1675 m.RecordRequestPrivacy(metrics.PrivacyLabels{ 1676 LMTEnforced: true, 1677 }) 1678 1679 // GDPR 1680 m.RecordRequestPrivacy(metrics.PrivacyLabels{ 1681 GDPREnforced: true, 1682 GDPRTCFVersion: metrics.TCFVersionErr, 1683 }) 1684 m.RecordRequestPrivacy(metrics.PrivacyLabels{ 1685 GDPREnforced: true, 1686 GDPRTCFVersion: metrics.TCFVersionV2, 1687 }) 1688 1689 assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA, 1690 float64(1), 1691 prometheus.Labels{ 1692 sourceLabel: sourceRequest, 1693 optOutLabel: "true", 1694 }) 1695 1696 assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA, 1697 float64(1), 1698 prometheus.Labels{ 1699 sourceLabel: sourceRequest, 1700 optOutLabel: "false", 1701 }) 1702 1703 assertCounterVecValue(t, "", "privacy_coppa", m.privacyCOPPA, 1704 float64(1), 1705 prometheus.Labels{ 1706 sourceLabel: sourceRequest, 1707 }) 1708 1709 assertCounterVecValue(t, "", "privacy_lmt", m.privacyLMT, 1710 float64(1), 1711 prometheus.Labels{ 1712 sourceLabel: sourceRequest, 1713 }) 1714 1715 assertCounterVecValue(t, "", "privacy_tcf:err", m.privacyTCF, 1716 float64(1), 1717 prometheus.Labels{ 1718 sourceLabel: sourceRequest, 1719 versionLabel: "err", 1720 }) 1721 1722 assertCounterVecValue(t, "", "privacy_tcf:v2", m.privacyTCF, 1723 float64(1), 1724 prometheus.Labels{ 1725 sourceLabel: sourceRequest, 1726 versionLabel: "v2", 1727 }) 1728 } 1729 1730 func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) { 1731 m := dto.Metric{} 1732 counter.Write(&m) 1733 actual := *m.GetCounter().Value 1734 1735 assert.Equal(t, expected, actual, description) 1736 } 1737 1738 func assertCounterVecValue(t *testing.T, description, name string, counterVec *prometheus.CounterVec, expected float64, labels prometheus.Labels) { 1739 counter := counterVec.With(labels) 1740 assertCounterValue(t, description, name, counter, expected) 1741 } 1742 1743 func getHistogramFromHistogramVec(histogram *prometheus.HistogramVec, labelKey, labelValue string) dto.Histogram { 1744 var result dto.Histogram 1745 processMetrics(histogram, func(m dto.Metric) { 1746 for _, label := range m.GetLabel() { 1747 if label.GetName() == labelKey && label.GetValue() == labelValue { 1748 result = *m.GetHistogram() 1749 } 1750 } 1751 }) 1752 return result 1753 } 1754 1755 func getHistogramFromHistogramVecByTwoKeys(histogram *prometheus.HistogramVec, label1Key, label1Value, label2Key, label2Value string) dto.Histogram { 1756 var result dto.Histogram 1757 processMetrics(histogram, func(m dto.Metric) { 1758 for ind, label := range m.GetLabel() { 1759 if label.GetName() == label1Key && label.GetValue() == label1Value { 1760 valInd := ind 1761 if ind == 1 { 1762 valInd = 0 1763 } else { 1764 valInd = 1 1765 } 1766 if m.Label[valInd].GetName() == label2Key && m.Label[valInd].GetValue() == label2Value { 1767 result = *m.GetHistogram() 1768 } 1769 } 1770 } 1771 }) 1772 return result 1773 } 1774 1775 func processMetrics(collector prometheus.Collector, handler func(m dto.Metric)) { 1776 collectorChan := make(chan prometheus.Metric) 1777 go func() { 1778 collector.Collect(collectorChan) 1779 close(collectorChan) 1780 }() 1781 1782 for metric := range collectorChan { 1783 dtoMetric := dto.Metric{} 1784 metric.Write(&dtoMetric) 1785 handler(dtoMetric) 1786 } 1787 } 1788 1789 func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expectedCount uint64, expectedSum float64) { 1790 assert.Equal(t, expectedCount, histogram.GetSampleCount(), name+":count") 1791 assert.Equal(t, expectedSum, histogram.GetSampleSum(), name+":sum") 1792 } 1793 1794 func TestRecordAdapterBuyerUIDScrubbed(t *testing.T) { 1795 1796 tests := []struct { 1797 name string 1798 disabled bool 1799 expectedCount float64 1800 }{ 1801 { 1802 name: "enabled", 1803 disabled: false, 1804 expectedCount: 1, 1805 }, 1806 { 1807 name: "disabled", 1808 disabled: true, 1809 expectedCount: 0, 1810 }, 1811 } 1812 1813 for _, tt := range tests { 1814 t.Run(tt.name, func(t *testing.T) { 1815 m := createMetricsForTesting() 1816 m.metricsDisabled.AdapterBuyerUIDScrubbed = tt.disabled 1817 adapterName := openrtb_ext.BidderName("AnyName") 1818 lowerCasedAdapterName := "anyname" 1819 m.RecordAdapterBuyerUIDScrubbed(adapterName) 1820 1821 assertCounterVecValue(t, 1822 "Increment adapter buyeruid scrubbed counter", 1823 "adapter_buyeruids_scrubbed", 1824 m.adapterScrubbedBuyerUIDs, 1825 tt.expectedCount, 1826 prometheus.Labels{ 1827 adapterLabel: lowerCasedAdapterName, 1828 }) 1829 }) 1830 } 1831 } 1832 1833 func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { 1834 m := createMetricsForTesting() 1835 adapterName := openrtb_ext.BidderName("AnyName") 1836 lowerCasedAdapterName := "anyname" 1837 m.RecordAdapterGDPRRequestBlocked(adapterName) 1838 1839 assertCounterVecValue(t, 1840 "Increment adapter GDPR request blocked counter", 1841 "adapter_gdpr_requests_blocked", 1842 m.adapterGDPRBlockedRequests, 1843 1, 1844 prometheus.Labels{ 1845 adapterLabel: lowerCasedAdapterName, 1846 }) 1847 } 1848 1849 func TestStoredResponsesMetric(t *testing.T) { 1850 testCases := []struct { 1851 description string 1852 publisherId string 1853 accountStoredResponsesMetricsDisabled bool 1854 expectedAccountStoredResponsesCount float64 1855 expectedStoredResponsesCount float64 1856 }{ 1857 1858 { 1859 description: "Publisher id is given, account stored responses enabled, expected both account stored responses and stored responses counter to have a record", 1860 publisherId: "acct-id", 1861 accountStoredResponsesMetricsDisabled: false, 1862 expectedAccountStoredResponsesCount: 1, 1863 expectedStoredResponsesCount: 1, 1864 }, 1865 { 1866 description: "Publisher id is given, account stored responses disabled, expected stored responses counter only to have a record", 1867 publisherId: "acct-id", 1868 accountStoredResponsesMetricsDisabled: true, 1869 expectedAccountStoredResponsesCount: 0, 1870 expectedStoredResponsesCount: 1, 1871 }, 1872 { 1873 description: "Publisher id is unknown, account stored responses enabled, expected stored responses counter only to have a record", 1874 publisherId: metrics.PublisherUnknown, 1875 accountStoredResponsesMetricsDisabled: true, 1876 expectedAccountStoredResponsesCount: 0, 1877 expectedStoredResponsesCount: 1, 1878 }, 1879 { 1880 description: "Publisher id is unknown, account stored responses disabled, expected stored responses counter only to have a record", 1881 publisherId: metrics.PublisherUnknown, 1882 accountStoredResponsesMetricsDisabled: false, 1883 expectedAccountStoredResponsesCount: 0, 1884 expectedStoredResponsesCount: 1, 1885 }, 1886 } 1887 1888 for _, test := range testCases { 1889 m := createMetricsForTesting() 1890 m.metricsDisabled.AccountStoredResponses = test.accountStoredResponsesMetricsDisabled 1891 m.RecordStoredResponse(test.publisherId) 1892 1893 assertCounterVecValue(t, "", "account stored responses", m.accountStoredResponses, test.expectedAccountStoredResponsesCount, prometheus.Labels{accountLabel: "acct-id"}) 1894 assertCounterValue(t, "", "stored responses", m.storedResponses, test.expectedStoredResponsesCount) 1895 } 1896 } 1897 1898 func TestRecordAdsCertReqMetric(t *testing.T) { 1899 testCases := []struct { 1900 description string 1901 requestSuccess bool 1902 expectedSuccessRequestsCount float64 1903 expectedFailedRequestsCount float64 1904 }{ 1905 { 1906 description: "Record failed request, expected success request count is 0 and failed request count is 1", 1907 requestSuccess: false, 1908 expectedSuccessRequestsCount: 0, 1909 expectedFailedRequestsCount: 1, 1910 }, 1911 { 1912 description: "Record successful request, expected success request count is 1 and failed request count is 0", 1913 requestSuccess: true, 1914 expectedSuccessRequestsCount: 1, 1915 expectedFailedRequestsCount: 0, 1916 }, 1917 } 1918 1919 for _, test := range testCases { 1920 m := createMetricsForTesting() 1921 m.RecordAdsCertReq(test.requestSuccess) 1922 assertCounterVecValue(t, test.description, "successfully signed requests", m.adsCertRequests, test.expectedSuccessRequestsCount, prometheus.Labels{successLabel: requestSuccessful}) 1923 assertCounterVecValue(t, test.description, "unsuccessfully signed requests", m.adsCertRequests, test.expectedFailedRequestsCount, prometheus.Labels{successLabel: requestFailed}) 1924 } 1925 } 1926 1927 func TestRecordAdsCertSignTime(t *testing.T) { 1928 type testIn struct { 1929 adsCertSignDuration time.Duration 1930 } 1931 type testOut struct { 1932 expDuration float64 1933 expCount uint64 1934 } 1935 testCases := []struct { 1936 description string 1937 in testIn 1938 out testOut 1939 }{ 1940 { 1941 description: "Five second AdsCert sign time", 1942 in: testIn{ 1943 adsCertSignDuration: time.Second * 5, 1944 }, 1945 out: testOut{ 1946 expDuration: 5, 1947 expCount: 1, 1948 }, 1949 }, 1950 { 1951 description: "Five millisecond AdsCert sign time", 1952 in: testIn{ 1953 adsCertSignDuration: time.Millisecond * 5, 1954 }, 1955 out: testOut{ 1956 expDuration: 0.005, 1957 expCount: 1, 1958 }, 1959 }, 1960 { 1961 description: "Zero AdsCert sign time", 1962 in: testIn{}, 1963 out: testOut{ 1964 expDuration: 0, 1965 expCount: 1, 1966 }, 1967 }, 1968 } 1969 for i, test := range testCases { 1970 pm := createMetricsForTesting() 1971 pm.RecordAdsCertSignTime(test.in.adsCertSignDuration) 1972 1973 m := dto.Metric{} 1974 pm.adsCertSignTimer.Write(&m) 1975 histogram := *m.GetHistogram() 1976 1977 assert.Equal(t, test.out.expCount, histogram.GetSampleCount(), "[%d] Incorrect number of histogram entries. Desc: %s\n", i, test.description) 1978 assert.Equal(t, test.out.expDuration, histogram.GetSampleSum(), "[%d] Incorrect number of histogram cumulative values. Desc: %s\n", i, test.description) 1979 } 1980 } 1981 1982 func TestRecordModuleMetrics(t *testing.T) { 1983 m := createMetricsForTesting() 1984 1985 // check that each module has its own metric recorded with correct stage labels 1986 for module, stages := range modulesStages { 1987 for _, stage := range stages { 1988 // first record the metrics 1989 m.RecordModuleCalled(metrics.ModuleLabels{ 1990 Module: module, 1991 Stage: stage, 1992 }, time.Millisecond*1) 1993 m.RecordModuleFailed(metrics.ModuleLabels{ 1994 Module: module, 1995 Stage: stage, 1996 }) 1997 m.RecordModuleSuccessNooped(metrics.ModuleLabels{ 1998 Module: module, 1999 Stage: stage, 2000 }) 2001 m.RecordModuleSuccessUpdated(metrics.ModuleLabels{ 2002 Module: module, 2003 Stage: stage, 2004 }) 2005 m.RecordModuleSuccessRejected(metrics.ModuleLabels{ 2006 Module: module, 2007 Stage: stage, 2008 }) 2009 m.RecordModuleExecutionError(metrics.ModuleLabels{ 2010 Module: module, 2011 Stage: stage, 2012 }) 2013 m.RecordModuleTimeout(metrics.ModuleLabels{ 2014 Module: module, 2015 Stage: stage, 2016 }) 2017 2018 // now check that the values are correct 2019 result := getHistogramFromHistogramVec(m.moduleDuration[module], stageLabel, stage) 2020 assertHistogram(t, fmt.Sprintf("module_%s_duration", module), result, 1, 0.001) 2021 assertCounterVecValue(t, "Module calls performed", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleCalls[module], 1, prometheus.Labels{stageLabel: stage}) 2022 assertCounterVecValue(t, "Module calls failed", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleFailures[module], 1, prometheus.Labels{stageLabel: stage}) 2023 assertCounterVecValue(t, "Module success noop action", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleSuccessNoops[module], 1, prometheus.Labels{stageLabel: stage}) 2024 assertCounterVecValue(t, "Module success update action", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleSuccessUpdates[module], 1, prometheus.Labels{stageLabel: stage}) 2025 assertCounterVecValue(t, "Module success reject action", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleSuccessRejects[module], 1, prometheus.Labels{stageLabel: stage}) 2026 assertCounterVecValue(t, "Module execution error", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleExecutionErrors[module], 1, prometheus.Labels{stageLabel: stage}) 2027 assertCounterVecValue(t, "Module timeout", fmt.Sprintf("%s metric recorded during %s stage", module, stage), m.moduleTimeouts[module], 1, prometheus.Labels{stageLabel: stage}) 2028 } 2029 } 2030 }