diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 2c8a12bf664..48437008d12 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -52,7 +52,7 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h defer deps.pbsAnalytics.LogCookieSyncObject(&co) - deps.metrics.RecordCookieSync(pbsmetrics.Labels{}) + deps.metrics.RecordCookieSync() userSyncCookie := usersync.ParsePBSCookieFromRequest(r, deps.hostCookie) if !userSyncCookie.AllowSyncs() { http.Error(w, "User has opted out", http.StatusUnauthorized) diff --git a/go.sum b/go.sum index 6d9e21f0582..57fcbb76428 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,10 @@ github.com/prebid/go-gdpr v0.6.0 h1:/GKrygGkUbsgd96HIkjAu7/6CHtRedvcojRtfAd4Igc= github.com/prebid/go-gdpr v0.6.0/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go index da8e812806c..81cfbfd0798 100644 --- a/pbsmetrics/config/metrics.go +++ b/pbsmetrics/config/metrics.go @@ -140,9 +140,9 @@ func (me *MultiMetricsEngine) RecordAdapterTime(labels pbsmetrics.AdapterLabels, } // RecordCookieSync across all engines -func (me *MultiMetricsEngine) RecordCookieSync(labels pbsmetrics.Labels) { +func (me *MultiMetricsEngine) RecordCookieSync() { for _, thisME := range *me { - thisME.RecordCookieSync(labels) + thisME.RecordCookieSync() } } @@ -175,9 +175,9 @@ func (me *MultiMetricsEngine) RecordUserIDSet(userLabels pbsmetrics.UserLabels) } // RecordPrebidCacheRequestTime across all engines -func (me *MultiMetricsEngine) RecordPrebidCacheRequestTime(labels pbsmetrics.RequestLabels, length time.Duration) { +func (me *MultiMetricsEngine) RecordPrebidCacheRequestTime(success bool, length time.Duration) { for _, thisME := range *me { - thisME.RecordPrebidCacheRequestTime(labels, length) + thisME.RecordPrebidCacheRequestTime(success, length) } } @@ -186,85 +186,68 @@ type DummyMetricsEngine struct{} // RecordRequest as a noop func (me *DummyMetricsEngine) RecordRequest(labels pbsmetrics.Labels) { - return } // RecordConnectionAccept as a noop func (me *DummyMetricsEngine) RecordConnectionAccept(success bool) { - return } // RecordConnectionClose as a noop func (me *DummyMetricsEngine) RecordConnectionClose(success bool) { - return } // RecordImps as a noop func (me *DummyMetricsEngine) RecordImps(implabels pbsmetrics.ImpLabels) { - return } // RecordLegacyImps as a noop func (me *DummyMetricsEngine) RecordLegacyImps(labels pbsmetrics.Labels, numImps int) { - return } // RecordRequestTime as a noop func (me *DummyMetricsEngine) RecordRequestTime(labels pbsmetrics.Labels, length time.Duration) { - return } // RecordAdapterPanic as a noop func (me *DummyMetricsEngine) RecordAdapterPanic(labels pbsmetrics.AdapterLabels) { - return } // RecordAdapterRequest as a noop func (me *DummyMetricsEngine) RecordAdapterRequest(labels pbsmetrics.AdapterLabels) { - return } // RecordAdapterBidReceived as a noop func (me *DummyMetricsEngine) RecordAdapterBidReceived(labels pbsmetrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { - return } // RecordAdapterPrice as a noop func (me *DummyMetricsEngine) RecordAdapterPrice(labels pbsmetrics.AdapterLabels, cpm float64) { - return } // RecordAdapterTime as a noop func (me *DummyMetricsEngine) RecordAdapterTime(labels pbsmetrics.AdapterLabels, length time.Duration) { - return } // RecordCookieSync as a noop -func (me *DummyMetricsEngine) RecordCookieSync(labels pbsmetrics.Labels) { - return +func (me *DummyMetricsEngine) RecordCookieSync() { } // RecordAdapterCookieSync as a noop func (me *DummyMetricsEngine) RecordAdapterCookieSync(adapter openrtb_ext.BidderName, gdprBlocked bool) { - return } // RecordUserIDSet as a noop func (me *DummyMetricsEngine) RecordUserIDSet(userLabels pbsmetrics.UserLabels) { - return } // RecordStoredReqCacheResult as a noop func (me *DummyMetricsEngine) RecordStoredReqCacheResult(cacheResult pbsmetrics.CacheResult, inc int) { - return } // RecordStoredImpCacheResult as a noop func (me *DummyMetricsEngine) RecordStoredImpCacheResult(cacheResult pbsmetrics.CacheResult, inc int) { - return } // RecordPrebidCacheRequestTime as a noop -func (me *DummyMetricsEngine) RecordPrebidCacheRequestTime(labels pbsmetrics.RequestLabels, length time.Duration) { - return +func (me *DummyMetricsEngine) RecordPrebidCacheRequestTime(success bool, length time.Duration) { } diff --git a/pbsmetrics/config/metrics_test.go b/pbsmetrics/config/metrics_test.go index 8f4202ff54d..ad817ba75a9 100644 --- a/pbsmetrics/config/metrics_test.go +++ b/pbsmetrics/config/metrics_test.go @@ -75,9 +75,6 @@ func TestMultiMetricsEngine(t *testing.T) { AudioImps: true, NativeImps: true, } - requestLabels := pbsmetrics.RequestLabels{ - RequestStatus: pbsmetrics.RequestStatusOK, - } for i := 0; i < 5; i++ { metricsEngine.RecordRequest(labels) metricsEngine.RecordImps(impTypeLabels) @@ -88,7 +85,7 @@ func TestMultiMetricsEngine(t *testing.T) { metricsEngine.RecordAdapterPrice(pubLabels, 1.34) metricsEngine.RecordAdapterBidReceived(pubLabels, openrtb_ext.BidTypeBanner, true) metricsEngine.RecordAdapterTime(pubLabels, time.Millisecond*20) - metricsEngine.RecordPrebidCacheRequestTime(requestLabels, time.Millisecond*20) + metricsEngine.RecordPrebidCacheRequestTime(true, time.Millisecond*20) } labelsBlacklist := []pbsmetrics.Labels{ { diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go index 699c11e1068..9b3dd65ff4e 100644 --- a/pbsmetrics/go_metrics.go +++ b/pbsmetrics/go_metrics.go @@ -478,7 +478,7 @@ func (me *Metrics) RecordAdapterTime(labels AdapterLabels, length time.Duration) } // RecordCookieSync implements a part of the MetricsEngine interface. Records a cookie sync request -func (me *Metrics) RecordCookieSync(labels Labels) { +func (me *Metrics) RecordCookieSync() { me.CookieSyncMeter.Mark(1) } @@ -518,13 +518,12 @@ func (me *Metrics) RecordStoredImpCacheResult(cacheResult CacheResult, inc int) // RecordPrebidCacheRequestTime implements a part of the MetricsEngine interface. Records the // amount of time taken to store the auction result in Prebid Cache. -func (me *Metrics) RecordPrebidCacheRequestTime(labels RequestLabels, length time.Duration) { - if labels.RequestStatus == RequestStatusOK { +func (me *Metrics) RecordPrebidCacheRequestTime(success bool, length time.Duration) { + if success { me.PrebidCacheRequestTimerSuccess.Update(length) - return + } else { + me.PrebidCacheRequestTimerError.Update(length) } - - me.PrebidCacheRequestTimerError.Update(length) } func doMark(bidder openrtb_ext.BidderName, meters map[openrtb_ext.BidderName]metrics.Meter) { diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go index 390d49f45a6..b403733dcc7 100644 --- a/pbsmetrics/go_metrics_test.go +++ b/pbsmetrics/go_metrics_test.go @@ -172,25 +172,21 @@ func TestNewMetricsWithDisabledConfig(t *testing.T) { assert.True(t, m.MetricsDisabled.AccountAdapterDetails, "Accound adapter metrics should be disabled") } -func TestRecordPrebidCacheRequestTimeWithSuccessLabel(t *testing.T) { +func TestRecordPrebidCacheRequestTimeWithSuccess(t *testing.T) { registry := metrics.NewRegistry() m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}) - m.RecordPrebidCacheRequestTime(RequestLabels{ - RequestStatus: RequestStatusOK, - }, 42) + m.RecordPrebidCacheRequestTime(true, 42) assert.Equal(t, m.PrebidCacheRequestTimerSuccess.Count(), int64(1)) assert.Equal(t, m.PrebidCacheRequestTimerError.Count(), int64(0)) } -func TestRecordPrebidCacheRequestTimeWithErrorLabel(t *testing.T) { +func TestRecordPrebidCacheRequestTimeWithNotSuccess(t *testing.T) { registry := metrics.NewRegistry() m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}) - m.RecordPrebidCacheRequestTime(RequestLabels{ - RequestStatus: RequestStatusErr, - }, 42) + m.RecordPrebidCacheRequestTime(false, 42) assert.Equal(t, m.PrebidCacheRequestTimerSuccess.Count(), int64(0)) assert.Equal(t, m.PrebidCacheRequestTimerError.Count(), int64(1)) diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go index 4808365e724..aea9735c276 100644 --- a/pbsmetrics/metrics.go +++ b/pbsmetrics/metrics.go @@ -236,6 +236,16 @@ const ( RequestActionErr RequestAction = "err" ) +// RequestActions returns possible setuid action labels +func RequestActions() []RequestAction { + return []RequestAction{ + RequestActionSet, + RequestActionOptOut, + RequestActionGDPR, + RequestActionErr, + } +} + // MetricsEngine is a generic interface to record PBS metrics into the desired backend // The first three metrics function fire off once per incoming request, so total metrics // will equal the total numer of incoming requests. The remaining 5 fire off per outgoing @@ -256,10 +266,10 @@ type MetricsEngine interface { RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) RecordAdapterPrice(labels AdapterLabels, cpm float64) RecordAdapterTime(labels AdapterLabels, length time.Duration) - RecordCookieSync(labels Labels) // May ignore all labels + RecordCookieSync() RecordAdapterCookieSync(adapter openrtb_ext.BidderName, gdprBlocked bool) RecordUserIDSet(userLabels UserLabels) // Function should verify bidder values RecordStoredReqCacheResult(cacheResult CacheResult, inc int) RecordStoredImpCacheResult(cacheResult CacheResult, inc int) - RecordPrebidCacheRequestTime(labels RequestLabels, length time.Duration) + RecordPrebidCacheRequestTime(success bool, length time.Duration) } diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go index c5e9a1965b6..6d57f9fcfaa 100644 --- a/pbsmetrics/metrics_mock.go +++ b/pbsmetrics/metrics_mock.go @@ -68,8 +68,8 @@ func (me *MetricsEngineMock) RecordAdapterTime(labels AdapterLabels, length time } // RecordCookieSync mock -func (me *MetricsEngineMock) RecordCookieSync(labels Labels) { - me.Called(labels) +func (me *MetricsEngineMock) RecordCookieSync() { + me.Called() } // RecordAdapterCookieSync mock @@ -93,6 +93,6 @@ func (me *MetricsEngineMock) RecordStoredImpCacheResult(cacheResult CacheResult, } // RecordPrebidCacheRequestTime mock -func (me *MetricsEngineMock) RecordPrebidCacheRequestTime(labels RequestLabels, length time.Duration) { - me.Called(labels, length) +func (me *MetricsEngineMock) RecordPrebidCacheRequestTime(success bool, length time.Duration) { + me.Called(success, length) } diff --git a/pbsmetrics/prometheus/preload.go b/pbsmetrics/prometheus/preload.go new file mode 100644 index 00000000000..7654dd54f82 --- /dev/null +++ b/pbsmetrics/prometheus/preload.go @@ -0,0 +1,147 @@ +package prometheusmetrics + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +func preloadLabelValues(m *Metrics) { + var ( + actionValues = actionsAsString() + adapterValues = adaptersAsString() + adapterErrorValues = adapterErrorsAsString() + bidTypeValues = []string{markupDeliveryAdm, markupDeliveryNurl} + boolValues = boolValuesAsString() + cacheResultValues = cacheResultsAsString() + cookieValues = cookieTypesAsString() + connectionErrorValues = []string{connectionAcceptError, connectionCloseError} + requestStatusValues = requestStatusesAsString() + requestTypeValues = requestTypesAsString() + ) + + preloadLabelValuesForCounter(m.connectionsError, map[string][]string{ + connectionErrorLabel: connectionErrorValues, + }) + + preloadLabelValuesForCounter(m.impressions, map[string][]string{ + isBannerLabel: boolValues, + isVideoLabel: boolValues, + isAudioLabel: boolValues, + isNativeLabel: boolValues, + }) + + preloadLabelValuesForHistogram(m.prebidCacheWriteTimer, map[string][]string{ + successLabel: boolValues, + }) + + preloadLabelValuesForCounter(m.requests, map[string][]string{ + requestTypeLabel: requestTypeValues, + requestStatusLabel: requestStatusValues, + }) + + preloadLabelValuesForHistogram(m.requestsTimer, map[string][]string{ + requestTypeLabel: requestTypeValues, + }) + + preloadLabelValuesForCounter(m.requestsWithoutCookie, map[string][]string{ + requestTypeLabel: requestTypeValues, + }) + + preloadLabelValuesForCounter(m.storedImpressionsCacheResult, map[string][]string{ + cacheResultLabel: cacheResultValues, + }) + + preloadLabelValuesForCounter(m.storedRequestCacheResult, map[string][]string{ + cacheResultLabel: cacheResultValues, + }) + + preloadLabelValuesForCounter(m.adapterBids, map[string][]string{ + adapterLabel: adapterValues, + markupDeliveryLabel: bidTypeValues, + }) + + preloadLabelValuesForCounter(m.adapterCookieSync, map[string][]string{ + adapterLabel: adapterValues, + privacyBlockedLabel: boolValues, + }) + + preloadLabelValuesForCounter(m.adapterErrors, map[string][]string{ + adapterLabel: adapterValues, + adapterErrorLabel: adapterErrorValues, + }) + + preloadLabelValuesForCounter(m.adapterPanics, map[string][]string{ + adapterLabel: adapterValues, + }) + + preloadLabelValuesForHistogram(m.adapterPrices, map[string][]string{ + adapterLabel: adapterValues, + }) + + preloadLabelValuesForCounter(m.adapterRequests, map[string][]string{ + adapterLabel: adapterValues, + cookieLabel: cookieValues, + hasBidsLabel: boolValues, + }) + + preloadLabelValuesForHistogram(m.adapterRequestsTimer, map[string][]string{ + adapterLabel: adapterValues, + }) + + preloadLabelValuesForCounter(m.adapterUserSync, map[string][]string{ + adapterLabel: adapterValues, + actionLabel: actionValues, + }) +} + +func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) { + registerLabelPermutations(labelsWithValues, func(labels prometheus.Labels) { + counter.With(labels) + }) +} + +func preloadLabelValuesForHistogram(histogram *prometheus.HistogramVec, labelsWithValues map[string][]string) { + registerLabelPermutations(labelsWithValues, func(labels prometheus.Labels) { + histogram.With(labels) + }) +} + +func registerLabelPermutations(labelsWithValues map[string][]string, register func(prometheus.Labels)) { + if len(labelsWithValues) == 0 { + return + } + + keys := make([]string, 0, len(labelsWithValues)) + values := make([][]string, 0, len(labelsWithValues)) + for k, v := range labelsWithValues { + keys = append(keys, k) + values = append(values, v) + } + + labels := prometheus.Labels{} + registerLabelPermutationsRecursive(0, keys, values, labels, register) +} + +func registerLabelPermutationsRecursive(depth int, keys []string, values [][]string, labels prometheus.Labels, register func(prometheus.Labels)) { + label := keys[depth] + isLeaf := depth == len(keys)-1 + + if isLeaf { + for _, v := range values[depth] { + labels[label] = v + register(cloneLabels(labels)) + } + } else { + for _, v := range values[depth] { + labels[label] = v + registerLabelPermutationsRecursive(depth+1, keys, values, labels, register) + } + } +} + +func cloneLabels(labels prometheus.Labels) prometheus.Labels { + clone := prometheus.Labels{} + for k, v := range labels { + clone[k] = v + } + return clone +} diff --git a/pbsmetrics/prometheus/preload_test.go b/pbsmetrics/prometheus/preload_test.go new file mode 100644 index 00000000000..7adc2f015b0 --- /dev/null +++ b/pbsmetrics/prometheus/preload_test.go @@ -0,0 +1,64 @@ +package prometheusmetrics + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/prometheus/client_golang/prometheus" +) + +func TestRegisterLabelPermutations(t *testing.T) { + testCases := []struct { + description string + labelsWithValues map[string][]string + expectedLabels []prometheus.Labels + }{ + { + description: "Empty set.", + labelsWithValues: map[string][]string{}, + expectedLabels: []prometheus.Labels{}, + }, + { + description: "Set of 1 label and 1 value.", + labelsWithValues: map[string][]string{ + "1": {"A"}, + }, + expectedLabels: []prometheus.Labels{ + {"1": "A"}, + }, + }, + { + description: "Set of 1 label and 2 values.", + labelsWithValues: map[string][]string{ + "1": {"A", "B"}, + }, + expectedLabels: []prometheus.Labels{ + {"1": "A"}, + {"1": "B"}, + }, + }, + { + description: "Set of 2 labels and 2 values.", + labelsWithValues: map[string][]string{ + "1": {"A", "B"}, + "2": {"C", "D"}, + }, + expectedLabels: []prometheus.Labels{ + {"1": "A", "2": "C"}, + {"1": "A", "2": "D"}, + {"1": "B", "2": "C"}, + {"1": "B", "2": "D"}, + }, + }, + } + + for _, test := range testCases { + resultLabels := []prometheus.Labels{} + registerLabelPermutations(test.labelsWithValues, func(label prometheus.Labels) { + resultLabels = append(resultLabels, label) + }) + + assert.ElementsMatch(t, test.expectedLabels, resultLabels) + } +} diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go index b46259de730..7cb80643542 100644 --- a/pbsmetrics/prometheus/prometheus.go +++ b/pbsmetrics/prometheus/prometheus.go @@ -1,198 +1,222 @@ package prometheusmetrics import ( + "strconv" "time" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbsmetrics" "github.com/prometheus/client_golang/prometheus" - _ "github.com/prometheus/client_golang/prometheus/promhttp" ) -// Defines the actual Prometheus metrics we will be using. Satisfies interface MetricsEngine +// Metrics defines the Prometheus metrics backing the MetricsEngine implementation. type Metrics struct { - Registry *prometheus.Registry - connCounter prometheus.Gauge - connError *prometheus.CounterVec - imps *prometheus.CounterVec - legacyImps *prometheus.CounterVec - requests *prometheus.CounterVec - reqTimer *prometheus.HistogramVec - adaptRequests *prometheus.CounterVec - adaptTimer *prometheus.HistogramVec - adaptBids *prometheus.CounterVec - adaptPrices *prometheus.HistogramVec - adaptErrors *prometheus.CounterVec - adaptPanics *prometheus.CounterVec - cookieSync prometheus.Counter - adaptCookieSync *prometheus.CounterVec - userID *prometheus.CounterVec - prebidCacheReqTimer *prometheus.HistogramVec - storedReqCacheResult *prometheus.CounterVec - storedImpCacheResult *prometheus.CounterVec + Registry *prometheus.Registry + + // General Metrics + connectionsClosed prometheus.Counter + connectionsError *prometheus.CounterVec + connectionsOpened prometheus.Counter + cookieSync prometheus.Counter + impressions *prometheus.CounterVec + impressionsLegacy prometheus.Counter + prebidCacheWriteTimer *prometheus.HistogramVec + requests *prometheus.CounterVec + requestsTimer *prometheus.HistogramVec + requestsWithoutCookie *prometheus.CounterVec + storedImpressionsCacheResult *prometheus.CounterVec + storedRequestCacheResult *prometheus.CounterVec + + // Adapter Metrics + adapterBids *prometheus.CounterVec + adapterCookieSync *prometheus.CounterVec + adapterErrors *prometheus.CounterVec + adapterPanics *prometheus.CounterVec + adapterPrices *prometheus.HistogramVec + adapterRequests *prometheus.CounterVec + adapterRequestsTimer *prometheus.HistogramVec + adapterUserSync *prometheus.CounterVec + + // Account Metrics + accountRequests *prometheus.CounterVec } const ( - requestTypeLabel = "request_type" - demandSourceLabel = "demand_source" - browserLabel = "browser" - cookieLabel = "cookie" - responseStatusLabel = "response_status" - adapterLabel = "adapter" - adapterBidLabel = "adapter_bid" - markupTypeLabel = "markup_type" - bidTypeLabel = "bid_type" - adapterErrLabel = "adapter_error" - cacheResultLabel = "cache_result" - gdprBlockedLabel = "gdpr_blocked" - bannerLabel = "banner" - videoLabel = "video" - audioLabel = "audio" - nativeLabel = "native" - accountLabel = "account" + accountLabel = "account" + actionLabel = "action" + adapterErrorLabel = "adapter_error" + adapterLabel = "adapter" + bidTypeLabel = "bid_type" + cacheResultLabel = "cache_result" + connectionErrorLabel = "connection_error" + cookieLabel = "cookie" + hasBidsLabel = "has_bids" + isAudioLabel = "audio" + isBannerLabel = "banner" + isNativeLabel = "native" + isVideoLabel = "video" + markupDeliveryLabel = "delivery" + privacyBlockedLabel = "privacy_blocked" + requestStatusLabel = "request_status" + requestTypeLabel = "request_type" + successLabel = "success" ) -// NewMetrics constructs the appropriate options for the Prometheus metrics. Needs to be fed the promethus config -// Its own function to keep the metric creation function cleaner. -func NewMetrics(cfg config.PrometheusMetrics) *Metrics { - timerBuckets := prometheus.LinearBuckets(0.05, 0.05, 20) - timerBuckets = append(timerBuckets, []float64{1.5, 2.0, 3.0, 5.0, 10.0, 50.0}...) - - timerBucketsQuickTasks := prometheus.LinearBuckets(0.005, 0.005, 20) - timerBucketsQuickTasks = append([]float64{0.001, 0.0015, 0.003}, timerBucketsQuickTasks...) - - standardLabelNames := []string{demandSourceLabel, requestTypeLabel, browserLabel, cookieLabel, responseStatusLabel, accountLabel} +const ( + connectionAcceptError = "accept" + connectionCloseError = "close" +) - adapterLabelNames := []string{demandSourceLabel, requestTypeLabel, browserLabel, cookieLabel, adapterBidLabel, adapterLabel} - bidLabelNames := []string{demandSourceLabel, requestTypeLabel, browserLabel, cookieLabel, adapterBidLabel, adapterLabel, bidTypeLabel, markupTypeLabel} - errorLabelNames := []string{demandSourceLabel, requestTypeLabel, browserLabel, cookieLabel, adapterErrLabel, adapterLabel} +const ( + markupDeliveryAdm = "adm" + markupDeliveryNurl = "nurl" +) - impLabelNames := []string{bannerLabel, videoLabel, audioLabel, nativeLabel} +// NewMetrics initializes a new Prometheus metrics instance with preloaded label values. +func NewMetrics(cfg config.PrometheusMetrics) *Metrics { + requestTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} + cacheWriteTimeBuckts := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1} + priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000} metrics := Metrics{} metrics.Registry = prometheus.NewRegistry() - metrics.connCounter = newConnCounter(cfg) - metrics.Registry.MustRegister(metrics.connCounter) - metrics.connError = newCounter(cfg, "active_connections_total", - "Errors reported on the connections coming in.", - []string{"ErrorType"}, - ) - metrics.Registry.MustRegister(metrics.connError) - metrics.imps = newCounter(cfg, "imps_requested", - "Count of Impressions by type and in total requested through PBS.", - impLabelNames, - ) - metrics.Registry.MustRegister(metrics.imps) - metrics.legacyImps = newCounter(cfg, "legacy_imps_requested", - "Total number of impressions requested through legacy PBS.", - standardLabelNames, - ) - metrics.Registry.MustRegister(metrics.legacyImps) - metrics.requests = newCounter(cfg, "requests_total", - "Total number of requests made to PBS.", - standardLabelNames, - ) - metrics.Registry.MustRegister(metrics.requests) - metrics.reqTimer = newHistogram(cfg, "request_time_seconds", - "Seconds to resolve each PBS request.", - standardLabelNames, timerBuckets, - ) - metrics.Registry.MustRegister(metrics.reqTimer) - metrics.adaptRequests = newCounter(cfg, "adapter_requests_total", - "Number of requests sent out to each bidder.", - adapterLabelNames, - ) - metrics.Registry.MustRegister(metrics.adaptRequests) - metrics.adaptPanics = newCounter(cfg, "adapter_panics_total", - "Number of panics generated by each bidder.", - adapterLabelNames, - ) - metrics.Registry.MustRegister(metrics.adaptPanics) - metrics.adaptTimer = newHistogram(cfg, "adapter_time_seconds", - "Seconds to resolve each request to a bidder.", - adapterLabelNames, timerBuckets, - ) - metrics.Registry.MustRegister(metrics.adaptTimer) - metrics.adaptBids = newCounter(cfg, "adapter_bids_received_total", - "Number of bids received from each bidder.", - bidLabelNames, - ) - metrics.Registry.MustRegister(metrics.adaptBids) - metrics.storedReqCacheResult = newCounter(cfg, "stored_request_cache_performance", - "Number of stored request cache hits vs miss", - []string{"cache_result"}, - ) - metrics.Registry.MustRegister(metrics.storedReqCacheResult) - metrics.storedImpCacheResult = newCounter(cfg, "stored_imp_cache_performance", - "Number of stored imp cache hits vs miss", - []string{"cache_result"}, - ) - metrics.Registry.MustRegister(metrics.storedImpCacheResult) - metrics.adaptPrices = newHistogram(cfg, "adapter_prices", - "Values of the bids from each bidder.", - adapterLabelNames, prometheus.LinearBuckets(0.1, 0.1, 200), - ) - metrics.Registry.MustRegister(metrics.adaptPrices) - metrics.adaptErrors = newCounter(cfg, "adapter_errors_total", - "Number of unique error types seen in each request to an adapter.", - errorLabelNames, - ) - metrics.Registry.MustRegister(metrics.adaptErrors) - metrics.cookieSync = newCookieSync(cfg) - metrics.Registry.MustRegister(metrics.cookieSync) - metrics.adaptCookieSync = newCounter(cfg, "cookie_sync_returns", - "Number of syncs generated for a bidder, and if they were subsequently blocked.", - []string{adapterLabel, gdprBlockedLabel}, - ) - metrics.Registry.MustRegister(metrics.adaptCookieSync) - metrics.userID = newCounter(cfg, "setuid_calls", - "Number of user ID syncs performed", - []string{"action", "bidder"}, - ) - metrics.Registry.MustRegister(metrics.userID) - metrics.prebidCacheReqTimer = newHistogram(cfg, "prebid_cache_request_time_seconds", - "Seconds to complete each PBC request.", - []string{responseStatusLabel}, timerBucketsQuickTasks, - ) - metrics.Registry.MustRegister(metrics.prebidCacheReqTimer) - - initializeTimeSeries(&metrics) - return &metrics -} + metrics.connectionsClosed = newCounterWithoutLabels(cfg, metrics.Registry, + "connections_closed", + "Count of successful connections closed to Prebid Server.") + + metrics.connectionsError = newCounter(cfg, metrics.Registry, + "connections_error", + "Count of errors for connection open and close attempts to Prebid Server labeled by type.", + []string{connectionErrorLabel}) + + metrics.connectionsOpened = newCounterWithoutLabels(cfg, metrics.Registry, + "connections_opened", + "Count of successful connections opened to Prebid Server.") + + metrics.cookieSync = newCounterWithoutLabels(cfg, metrics.Registry, + "cookie_sync_requests", + "Count of cookie sync requests to Prebid Server.") + + metrics.impressions = newCounter(cfg, metrics.Registry, + "impressions_requests", + "Count of requested impressions to Prebid Server labeled by type.", + []string{isBannerLabel, isVideoLabel, isAudioLabel, isNativeLabel}) + + metrics.impressionsLegacy = newCounterWithoutLabels(cfg, metrics.Registry, + "impressions_requests_legacy", + "Count of requested impressions to Prebid Server using the legacy endpoint.") + + metrics.prebidCacheWriteTimer = newHistogram(cfg, metrics.Registry, + "prebidcache_write_time_seconds", + "Seconds to write to Prebid Cache labeled by success or failure. Failure timing is limited by Prebid Server enforced timeouts.", + []string{successLabel}, + cacheWriteTimeBuckts) + + metrics.requests = newCounter(cfg, metrics.Registry, + "requests", + "Count of total requests to Prebid Server labeled by type and status.", + []string{requestTypeLabel, requestStatusLabel}) + + metrics.requestsTimer = newHistogram(cfg, metrics.Registry, + "request_time_seconds", + "Seconds to resolve successful Prebid Server requests labeled by type.", + []string{requestTypeLabel}, + requestTimeBuckets) + + metrics.requestsWithoutCookie = newCounter(cfg, metrics.Registry, + "requests_without_cookie", + "Count of total requests to Prebid Server without a cookie labeled by type.", + []string{requestTypeLabel}) + + metrics.storedImpressionsCacheResult = newCounter(cfg, metrics.Registry, + "stored_impressions_cache_performance", + "Count of stored impression cache requests attempts by hits or miss.", + []string{cacheResultLabel}) + + metrics.storedRequestCacheResult = newCounter(cfg, metrics.Registry, + "stored_request_cache_performance", + "Count of stored request cache requests attempts by hits or miss.", + []string{cacheResultLabel}) + + metrics.adapterBids = newCounter(cfg, metrics.Registry, + "adapter_bids", + "Count of bids labeled by adapter and markup delivery type (adm or nurl).", + []string{adapterLabel, markupDeliveryLabel}) + + metrics.adapterCookieSync = newCounter(cfg, metrics.Registry, + "adapter_cookie_sync", + "Count of cookie sync requests received labeled by adapter and if the sync was blocked due to privacy regulation (GDPR, CCPA, etc...).", + []string{adapterLabel, privacyBlockedLabel}) + + metrics.adapterErrors = newCounter(cfg, metrics.Registry, + "adapter_errors", + "Count of errors labeled by adapter and error type.", + []string{adapterLabel, adapterErrorLabel}) + + metrics.adapterPanics = newCounter(cfg, metrics.Registry, + "adapter_panics", + "Count of panics labeled by adapter.", + []string{adapterLabel}) + + metrics.adapterPrices = newHistogram(cfg, metrics.Registry, + "adapter_prices", + "Monetary value of the bids labeled by adapter.", + []string{adapterLabel}, + priceBuckets) + + metrics.adapterRequests = newCounter(cfg, metrics.Registry, + "adapter_requests", + "Count of requests labeled by adapter, if has a cookie, and if it resulted in bids.", + []string{adapterLabel, cookieLabel, hasBidsLabel}) + + metrics.adapterRequestsTimer = newHistogram(cfg, metrics.Registry, + "adapter_request_time_seconds", + "Seconds to resolve each successful request labeled by adapter.", + []string{adapterLabel}, + requestTimeBuckets) + + metrics.adapterUserSync = newCounter(cfg, metrics.Registry, + "adapter_user_sync", + "Count of user ID sync requests received labeled by adapter and action.", + []string{adapterLabel, actionLabel}) + + metrics.accountRequests = newCounter(cfg, metrics.Registry, + "account_requests", + "Count of total requests to Prebid Server labeled by account.", + []string{accountLabel}) + + preloadLabelValues(&metrics) -func newConnCounter(cfg config.PrometheusMetrics) prometheus.Gauge { - opts := prometheus.GaugeOpts{ - Namespace: cfg.Namespace, - Subsystem: cfg.Subsystem, - Name: "active_connections", - Help: "Current number of active (open) connections.", - } - return prometheus.NewGauge(opts) + return &metrics } -func newCookieSync(cfg config.PrometheusMetrics) prometheus.Counter { +func newCounter(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string) *prometheus.CounterVec { opts := prometheus.CounterOpts{ Namespace: cfg.Namespace, Subsystem: cfg.Subsystem, - Name: "cookie_sync_requests_total", - Help: "Number of cookie sync requests received.", + Name: name, + Help: help, } - return prometheus.NewCounter(opts) + counter := prometheus.NewCounterVec(opts, labels) + registry.MustRegister(counter) + return counter } -func newCounter(cfg config.PrometheusMetrics, name string, help string, labels []string) *prometheus.CounterVec { +func newCounterWithoutLabels(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string) prometheus.Counter { opts := prometheus.CounterOpts{ Namespace: cfg.Namespace, Subsystem: cfg.Subsystem, Name: name, Help: help, } - return prometheus.NewCounterVec(opts, labels) + counter := prometheus.NewCounter(opts) + registry.MustRegister(counter) + return counter } -func newHistogram(cfg config.PrometheusMetrics, name string, help string, labels []string, buckets []float64) *prometheus.HistogramVec { +func newHistogram(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string, buckets []float64) *prometheus.HistogramVec { opts := prometheus.HistogramOpts{ Namespace: cfg.Namespace, Subsystem: cfg.Subsystem, @@ -200,386 +224,153 @@ func newHistogram(cfg config.PrometheusMetrics, name string, help string, labels Help: help, Buckets: buckets, } - return prometheus.NewHistogramVec(opts, labels) + histogram := prometheus.NewHistogramVec(opts, labels) + registry.MustRegister(histogram) + return histogram } -func (me *Metrics) RecordConnectionAccept(success bool) { +func (m *Metrics) RecordConnectionAccept(success bool) { if success { - me.connCounter.Inc() + m.connectionsOpened.Inc() } else { - me.connError.WithLabelValues("accept_error").Inc() + m.connectionsError.With(prometheus.Labels{ + connectionErrorLabel: connectionAcceptError, + }).Inc() } - } -func (me *Metrics) RecordConnectionClose(success bool) { +func (m *Metrics) RecordConnectionClose(success bool) { if success { - me.connCounter.Dec() + m.connectionsClosed.Inc() } else { - me.connError.WithLabelValues("close_error").Inc() - } -} - -func (me *Metrics) RecordRequest(labels pbsmetrics.Labels) { - me.requests.With(resolveLabels(labels)).Inc() -} - -func (me *Metrics) RecordImps(implabels pbsmetrics.ImpLabels) { - me.imps.With(resolveImpLabels(implabels)).Inc() -} - -func (me *Metrics) RecordLegacyImps(labels pbsmetrics.Labels, numImps int) { - var lbls prometheus.Labels - lbls = resolveLabels(labels) - me.legacyImps.With(lbls).Add(float64(numImps)) -} - -func (me *Metrics) RecordRequestTime(labels pbsmetrics.Labels, length time.Duration) { - time := float64(length) / float64(time.Second) - me.reqTimer.With(resolveLabels(labels)).Observe(time) -} - -func (me *Metrics) RecordAdapterPanic(labels pbsmetrics.AdapterLabels) { - me.adaptPanics.With(resolveAdapterLabels(labels)).Inc() -} - -func (me *Metrics) RecordAdapterRequest(labels pbsmetrics.AdapterLabels) { - me.adaptRequests.With(resolveAdapterLabels(labels)).Inc() - for k := range labels.AdapterErrors { - me.adaptErrors.With(resolveAdapterErrorLabels(labels, string(k))).Inc() - } -} - -func (me *Metrics) RecordAdapterBidReceived(labels pbsmetrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { - me.adaptBids.With(resolveBidLabels(labels, bidType, hasAdm)).Inc() -} - -func (me *Metrics) RecordAdapterPrice(labels pbsmetrics.AdapterLabels, cpm float64) { - me.adaptPrices.With(resolveAdapterLabels(labels)).Observe(cpm) -} - -func (me *Metrics) RecordAdapterTime(labels pbsmetrics.AdapterLabels, length time.Duration) { - time := float64(length) / float64(time.Second) - me.adaptTimer.With(resolveAdapterLabels(labels)).Observe(time) -} - -func (me *Metrics) RecordCookieSync(labels pbsmetrics.Labels) { - me.cookieSync.Inc() -} - -func (me *Metrics) RecordAdapterCookieSync(adapter openrtb_ext.BidderName, gdprBlocked bool) { - labels := prometheus.Labels{ - adapterLabel: string(adapter), - } - if gdprBlocked { - labels[gdprBlockedLabel] = "true" - } else { - labels[gdprBlockedLabel] = "false" - } - me.adaptCookieSync.With(labels).Inc() -} - -// RecordStoredReqCacheResult records cache hits and misses when looking up stored requests -func (me *Metrics) RecordStoredReqCacheResult(cacheResult pbsmetrics.CacheResult, inc int) { - labels := prometheus.Labels{ - cacheResultLabel: string(cacheResult), - } - - me.storedReqCacheResult.With(labels).Add(float64(inc)) -} - -// RecordStoredImpCacheResult records cache hits and misses when looking up stored imps -func (me *Metrics) RecordStoredImpCacheResult(cacheResult pbsmetrics.CacheResult, inc int) { - labels := prometheus.Labels{ - cacheResultLabel: string(cacheResult), + m.connectionsError.With(prometheus.Labels{ + connectionErrorLabel: connectionCloseError, + }).Inc() } - - me.storedImpCacheResult.With(labels).Add(float64(inc)) } -func (me *Metrics) RecordUserIDSet(userLabels pbsmetrics.UserLabels) { - me.userID.With(resolveUserSyncLabels(userLabels)).Inc() -} +func (m *Metrics) RecordRequest(labels pbsmetrics.Labels) { + m.requests.With(prometheus.Labels{ + requestTypeLabel: string(labels.RType), + requestStatusLabel: string(labels.RequestStatus), + }).Inc() -// RecordPrebidCacheRequestTime records amount of time taken to store the auction result in Prebid Cache -func (me *Metrics) RecordPrebidCacheRequestTime(labels pbsmetrics.RequestLabels, length time.Duration) { - time := float64(length) / float64(time.Second) - me.prebidCacheReqTimer.With(resolveRequestLabels(labels)).Observe(time) -} - -func resolveLabels(labels pbsmetrics.Labels) prometheus.Labels { - return prometheus.Labels{ - demandSourceLabel: string(labels.Source), - requestTypeLabel: string(labels.RType), - accountLabel: string(labels.PubID), - browserLabel: string(labels.Browser), - cookieLabel: string(labels.CookieFlag), - responseStatusLabel: string(labels.RequestStatus), + if labels.CookieFlag == pbsmetrics.CookieFlagNo { + m.requestsWithoutCookie.With(prometheus.Labels{ + requestTypeLabel: string(labels.RType), + }).Inc() } -} -func resolveAdapterLabels(labels pbsmetrics.AdapterLabels) prometheus.Labels { - return prometheus.Labels{ - demandSourceLabel: string(labels.Source), - requestTypeLabel: string(labels.RType), - // "pubid": labels.PubID, - browserLabel: string(labels.Browser), - cookieLabel: string(labels.CookieFlag), - adapterBidLabel: string(labels.AdapterBids), - adapterLabel: string(labels.Adapter), + if labels.PubID != pbsmetrics.PublisherUnknown { + m.accountRequests.With(prometheus.Labels{ + accountLabel: labels.PubID, + }).Inc() } } -func resolveBidLabels(labels pbsmetrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) prometheus.Labels { - bidLabels := prometheus.Labels{ - demandSourceLabel: string(labels.Source), - requestTypeLabel: string(labels.RType), - // "pubid": labels.PubID, - browserLabel: string(labels.Browser), - cookieLabel: string(labels.CookieFlag), - adapterBidLabel: string(labels.AdapterBids), - adapterLabel: string(labels.Adapter), - bidTypeLabel: string(bidType), - markupTypeLabel: "unknown", - } - if hasAdm { - bidLabels[markupTypeLabel] = "adm" - } - return bidLabels +func (m *Metrics) RecordImps(labels pbsmetrics.ImpLabels) { + m.impressions.With(prometheus.Labels{ + isBannerLabel: strconv.FormatBool(labels.BannerImps), + isVideoLabel: strconv.FormatBool(labels.VideoImps), + isAudioLabel: strconv.FormatBool(labels.AudioImps), + isNativeLabel: strconv.FormatBool(labels.NativeImps), + }).Inc() } -func resolveAdapterErrorLabels(labels pbsmetrics.AdapterLabels, errorType string) prometheus.Labels { - return prometheus.Labels{ - demandSourceLabel: string(labels.Source), - requestTypeLabel: string(labels.RType), - // "pubid": labels.PubID, - browserLabel: string(labels.Browser), - cookieLabel: string(labels.CookieFlag), - adapterErrLabel: errorType, - adapterLabel: string(labels.Adapter), - } +func (m *Metrics) RecordLegacyImps(labels pbsmetrics.Labels, numImps int) { + m.impressionsLegacy.Add(float64(numImps)) } -func resolveUserSyncLabels(userLabels pbsmetrics.UserLabels) prometheus.Labels { - return prometheus.Labels{ - "action": string(userLabels.Action), - "bidder": string(userLabels.Bidder), +func (m *Metrics) RecordRequestTime(labels pbsmetrics.Labels, length time.Duration) { + if labels.RequestStatus == pbsmetrics.RequestStatusOK { + m.requestsTimer.With(prometheus.Labels{ + requestTypeLabel: string(labels.RType), + }).Observe(length.Seconds()) } } -func resolveImpLabels(labels pbsmetrics.ImpLabels) prometheus.Labels { - var impLabels prometheus.Labels = prometheus.Labels{ - bannerLabel: "no", - videoLabel: "no", - audioLabel: "no", - nativeLabel: "no", - } - if labels.BannerImps { - impLabels[bannerLabel] = "yes" - } - if labels.VideoImps { - impLabels[videoLabel] = "yes" - } - if labels.AudioImps { - impLabels[audioLabel] = "yes" - } - if labels.NativeImps { - impLabels[nativeLabel] = "yes" - } - return impLabels -} +func (m *Metrics) RecordAdapterRequest(labels pbsmetrics.AdapterLabels) { + m.adapterRequests.With(prometheus.Labels{ + adapterLabel: string(labels.Adapter), + cookieLabel: string(labels.CookieFlag), + hasBidsLabel: strconv.FormatBool(labels.AdapterBids == pbsmetrics.AdapterBidPresent), + }).Inc() -func resolveRequestLabels(labels pbsmetrics.RequestLabels) prometheus.Labels { - return prometheus.Labels{ - responseStatusLabel: string(labels.RequestStatus), + for err := range labels.AdapterErrors { + m.adapterErrors.With(prometheus.Labels{ + adapterLabel: string(labels.Adapter), + adapterErrorLabel: string(err), + }).Inc() } } -// initializeTimeSeries precreates all possible metric label values, so there is no locking needed at run time creating new instances -func initializeTimeSeries(m *Metrics) { - // Connection errors - labels := addDimension([]prometheus.Labels{}, "ErrorType", []string{"accept_error", "close_error"}) - for _, l := range labels { - _ = m.connError.With(l) - } - - // Standard labels - labels = addDimension([]prometheus.Labels{}, demandSourceLabel, demandTypesAsString()) - labels = addDimension(labels, requestTypeLabel, requestTypesAsString()) - labels = addDimension(labels, browserLabel, browserTypesAsString()) - labels = addDimension(labels, cookieLabel, cookieTypesAsString()) - adapterLabels := labels // save regenerating these dimensions for adapter status - labels = addDimension(labels, responseStatusLabel, requestStatusesAsString()) - // If we implement an account whitelist, we can seed the metrics with that list to redusce latency associated with registering new lable values on the fly. - labels = addDimension(labels, accountLabel, []string{pbsmetrics.PublisherUnknown}) - for _, l := range labels { - _ = m.requests.With(l) - _ = m.reqTimer.With(l) - } - - // Adapter labels - labels = addDimension(adapterLabels, adapterLabel, adaptersAsString()) - errorLabels := labels // save regenerating these dimensions for adapter errors - labels = addDimension(labels, adapterBidLabel, adapterBidsAsString()) - for _, l := range labels { - _ = m.adaptRequests.With(l) - _ = m.adaptTimer.With(l) - _ = m.adaptPrices.With(l) - _ = m.adaptPanics.With(l) - } - - // AdapterBid labels - labels = addDimension(labels, bidTypeLabel, bidTypesAsString()) - labels = addDimension(labels, markupTypeLabel, []string{"unknown", "adm"}) - for _, l := range labels { - _ = m.adaptBids.With(l) - } - labels = addDimension(errorLabels, adapterErrLabel, adapterErrorsAsString()) - for _, l := range labels { - _ = m.adaptErrors.With(l) - } - cookieLabels := addDimension([]prometheus.Labels{}, adapterLabel, adaptersAsString()) - cookieLabels = addDimension(cookieLabels, gdprBlockedLabel, []string{"true", "false"}) - for _, l := range cookieLabels { - _ = m.adaptCookieSync.With(l) - } - cacheLabels := addDimension([]prometheus.Labels{}, "cache_result", cacheResultAsString()) - for _, l := range cacheLabels { - _ = m.storedImpCacheResult.With(l) - _ = m.storedReqCacheResult.With(l) - } - - // ImpType labels - impTypeLabels := addDimension([]prometheus.Labels{}, bannerLabel, []string{"yes", "no"}) - impTypeLabels = addDimension(impTypeLabels, videoLabel, []string{"yes", "no"}) - impTypeLabels = addDimension(impTypeLabels, audioLabel, []string{"yes", "no"}) - impTypeLabels = addDimension(impTypeLabels, nativeLabel, []string{"yes", "no"}) - for _, l := range impTypeLabels { - _ = m.imps.With(l) - } +func (m *Metrics) RecordAdapterPanic(labels pbsmetrics.AdapterLabels) { + m.adapterPanics.With(prometheus.Labels{ + adapterLabel: string(labels.Adapter), + }).Inc() } -// addDimesion will expand a slice of labels to add the dimension of a new set of values for a new label name -func addDimension(labels []prometheus.Labels, field string, values []string) []prometheus.Labels { - if len(labels) == 0 { - // We are starting a new slice of labels, so we can't loop. - return addToLabel(make(prometheus.Labels), field, values) - } - newLabels := make([]prometheus.Labels, 0, len(labels)*len(values)) - for _, l := range labels { - newLabels = append(newLabels, addToLabel(l, field, values)...) +func (m *Metrics) RecordAdapterBidReceived(labels pbsmetrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { + markupDelivery := markupDeliveryNurl + if hasAdm { + markupDelivery = markupDeliveryAdm } - return newLabels -} -// addToLabel will create a slice of labels adding a set of values tied to a label name. -func addToLabel(label prometheus.Labels, field string, values []string) []prometheus.Labels { - newLabels := make([]prometheus.Labels, len(values)) - for i, v := range values { - l := copyLabel(label) - l[field] = v - newLabels[i] = l - } - return newLabels + m.adapterBids.With(prometheus.Labels{ + adapterLabel: string(labels.Adapter), + markupDeliveryLabel: markupDelivery, + }).Inc() } -// Need to be able to deep copy prometheus labels. -func copyLabel(label prometheus.Labels) prometheus.Labels { - newLabel := make(prometheus.Labels) - for k, v := range label { - newLabel[k] = v - } - return newLabel +func (m *Metrics) RecordAdapterPrice(labels pbsmetrics.AdapterLabels, cpm float64) { + m.adapterPrices.With(prometheus.Labels{ + adapterLabel: string(labels.Adapter), + }).Observe(cpm) } -func demandTypesAsString() []string { - list := pbsmetrics.DemandTypes() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) +func (m *Metrics) RecordAdapterTime(labels pbsmetrics.AdapterLabels, length time.Duration) { + if len(labels.AdapterErrors) == 0 { + m.adapterRequestsTimer.With(prometheus.Labels{ + adapterLabel: string(labels.Adapter), + }).Observe(length.Seconds()) } - return output } -func requestTypesAsString() []string { - list := pbsmetrics.RequestTypes() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) - } - return output -} - -func browserTypesAsString() []string { - list := pbsmetrics.BrowserTypes() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) - } - return output +func (m *Metrics) RecordCookieSync() { + m.cookieSync.Inc() } -func cookieTypesAsString() []string { - list := pbsmetrics.CookieTypes() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) - } - return output +func (m *Metrics) RecordAdapterCookieSync(adapter openrtb_ext.BidderName, privacyBlocked bool) { + m.adapterCookieSync.With(prometheus.Labels{ + adapterLabel: string(adapter), + privacyBlockedLabel: strconv.FormatBool(privacyBlocked), + }).Inc() } -func requestStatusesAsString() []string { - list := pbsmetrics.RequestStatuses() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) +func (m *Metrics) RecordUserIDSet(labels pbsmetrics.UserLabels) { + adapter := string(labels.Bidder) + if adapter != "" { + m.adapterUserSync.With(prometheus.Labels{ + adapterLabel: adapter, + actionLabel: string(labels.Action), + }).Inc() } - return output } -func adapterBidsAsString() []string { - list := pbsmetrics.AdapterBids() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) - } - return output -} - -func adapterErrorsAsString() []string { - list := pbsmetrics.AdapterErrors() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) - } - return output -} - -func cacheResultAsString() []string { - list := pbsmetrics.CacheResults() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) - } - return output +func (m *Metrics) RecordStoredReqCacheResult(cacheResult pbsmetrics.CacheResult, inc int) { + m.storedRequestCacheResult.With(prometheus.Labels{ + cacheResultLabel: string(cacheResult), + }).Add(float64(inc)) } -func adaptersAsString() []string { - list := openrtb_ext.BidderList() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) - } - return output - +func (m *Metrics) RecordStoredImpCacheResult(cacheResult pbsmetrics.CacheResult, inc int) { + m.storedImpressionsCacheResult.With(prometheus.Labels{ + cacheResultLabel: string(cacheResult), + }).Add(float64(inc)) } -func bidTypesAsString() []string { - list := openrtb_ext.BidTypes() - output := make([]string, len(list)) - for i, s := range list { - output[i] = string(s) - } - return output - +func (m *Metrics) RecordPrebidCacheRequestTime(success bool, length time.Duration) { + m.prebidCacheWriteTimer.With(prometheus.Labels{ + successLabel: strconv.FormatBool(success), + }).Observe(length.Seconds()) } diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go index 0930e3fee1a..4cf9676e1d4 100644 --- a/pbsmetrics/prometheus/prometheus_test.go +++ b/pbsmetrics/prometheus/prometheus_test.go @@ -1,9 +1,6 @@ package prometheusmetrics import ( - "fmt" - "regexp" - "strconv" "testing" "time" @@ -15,619 +12,915 @@ import ( "github.com/stretchr/testify/assert" ) -var gaugeValueRegexp = regexp.MustCompile("gauge:") -var counterValueRegexp = regexp.MustCompile("counter:") -var histogramValueRegexp = regexp.MustCompile("histogram: register the metrics to prometheus - metrics := newTestMetricsEngine() +func TestUserIDSetMetricWhenBidderEmpty(t *testing.T) { + m := createMetricsForTesting() + action := pbsmetrics.RequestActionErr - // Get at the underlying metrics object - proMetrics := metrics + m.RecordUserIDSet(pbsmetrics.UserLabels{ + Bidder: openrtb_ext.BidderName(""), + Action: action, + }) - if err := proMetrics.Registry.Register(prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: "prebid", - Subsystem: "server", - Name: "active_connections", - Help: "Current number of active (open) connections.", - })); err == nil { - t.Error("connCounter not registered") - } + expectedTotalCount := float64(0) + actualTotalCount := float64(0) + processMetrics(m.adapterUserSync, func(m dto.Metric) { + actualTotalCount += m.GetCounter().GetValue() + }) + assert.Equal(t, expectedTotalCount, actualTotalCount, "total count") } -func newTestMetricsEngine() *Metrics { - return NewMetrics(config.PrometheusMetrics{ - Port: 8080, - Namespace: "prebid", - Subsystem: "server", +func TestAdapterPanicMetric(t *testing.T) { + m := createMetricsForTesting() + adapterName := "anyName" + + m.RecordAdapterPanic(pbsmetrics.AdapterLabels{ + Adapter: openrtb_ext.BidderName(adapterName), }) + + expectedCount := float64(1) + assertCounterVecValue(t, "", "adapterPanics", m.adapterPanics, + expectedCount, + prometheus.Labels{ + adapterLabel: adapterName, + }) } -var labels = []pbsmetrics.Labels{ - { - Source: pbsmetrics.DemandWeb, - RType: pbsmetrics.ReqTypeLegacy, - PubID: "Pub1", - Browser: pbsmetrics.BrowserOther, - CookieFlag: pbsmetrics.CookieFlagYes, - RequestStatus: pbsmetrics.RequestStatusOK, - }, - { - Source: pbsmetrics.DemandWeb, - RType: pbsmetrics.ReqTypeLegacy, - PubID: "Pub1", - Browser: pbsmetrics.BrowserSafari, - CookieFlag: pbsmetrics.CookieFlagYes, - RequestStatus: pbsmetrics.RequestStatusOK, - }, - { - Source: pbsmetrics.DemandApp, - RType: pbsmetrics.ReqTypeORTB2Web, - PubID: "Pub2", - Browser: pbsmetrics.BrowserOther, - CookieFlag: pbsmetrics.CookieFlagNo, - RequestStatus: pbsmetrics.RequestStatusOK, - }, - { - Source: pbsmetrics.DemandUnknown, - RType: pbsmetrics.ReqTypeORTB2App, - PubID: "Pub3", - Browser: pbsmetrics.BrowserOther, - CookieFlag: pbsmetrics.CookieFlagUnknown, - RequestStatus: pbsmetrics.RequestStatusBadInput, - }, - { - Source: pbsmetrics.DemandWeb, - RType: pbsmetrics.ReqTypeVideo, - PubID: "Pub4", - Browser: pbsmetrics.BrowserOther, - CookieFlag: pbsmetrics.CookieFlagUnknown, - RequestStatus: pbsmetrics.RequestStatusOK, - }, +func TestStoredReqCacheResultMetric(t *testing.T) { + m := createMetricsForTesting() + + hitCount := 42 + missCount := 108 + m.RecordStoredReqCacheResult(pbsmetrics.CacheHit, hitCount) + m.RecordStoredReqCacheResult(pbsmetrics.CacheMiss, missCount) + + assertCounterVecValue(t, "", "storedRequestCacheResult:hit", m.storedRequestCacheResult, + float64(hitCount), + prometheus.Labels{ + cacheResultLabel: string(pbsmetrics.CacheHit), + }) + assertCounterVecValue(t, "", "storedRequestCacheResult:miss", m.storedRequestCacheResult, + float64(missCount), + prometheus.Labels{ + cacheResultLabel: string(pbsmetrics.CacheMiss), + }) } -var impTypeLabels = []pbsmetrics.ImpLabels{ - { - //impType: "banner", - BannerImps: true, - VideoImps: false, - AudioImps: false, - NativeImps: false, - }, - { - //impType: "audio", - BannerImps: false, - VideoImps: true, - AudioImps: false, - NativeImps: false, - }, - { - //impType: "video", - BannerImps: false, - VideoImps: false, - AudioImps: true, - NativeImps: false, - }, - { - //impType: "native", - BannerImps: false, - VideoImps: false, - AudioImps: false, - NativeImps: true, - }, +func TestStoredImpCacheResultMetric(t *testing.T) { + m := createMetricsForTesting() + + hitCount := 42 + missCount := 108 + m.RecordStoredImpCacheResult(pbsmetrics.CacheHit, hitCount) + m.RecordStoredImpCacheResult(pbsmetrics.CacheMiss, missCount) + + assertCounterVecValue(t, "", "storedImpressionsCacheResult:hit", m.storedImpressionsCacheResult, + float64(hitCount), + prometheus.Labels{ + cacheResultLabel: string(pbsmetrics.CacheHit), + }) + assertCounterVecValue(t, "", "storedImpressionsCacheResult:miss", m.storedImpressionsCacheResult, + float64(missCount), + prometheus.Labels{ + cacheResultLabel: string(pbsmetrics.CacheMiss), + }) } -var s struct{} - -var adaptLabels = []pbsmetrics.AdapterLabels{ - { - Source: pbsmetrics.DemandWeb, - RType: pbsmetrics.ReqTypeLegacy, - Adapter: openrtb_ext.BidderAppnexus, - PubID: "Pub1", - Browser: pbsmetrics.BrowserOther, - CookieFlag: pbsmetrics.CookieFlagYes, - AdapterBids: pbsmetrics.AdapterBidPresent, - AdapterErrors: map[pbsmetrics.AdapterError]struct{}{}, - }, - { - Source: pbsmetrics.DemandWeb, - RType: pbsmetrics.ReqTypeLegacy, - Adapter: openrtb_ext.BidderEPlanning, - PubID: "Pub1", - Browser: pbsmetrics.BrowserSafari, - CookieFlag: pbsmetrics.CookieFlagYes, - AdapterBids: pbsmetrics.AdapterBidPresent, - AdapterErrors: map[pbsmetrics.AdapterError]struct{}{ - pbsmetrics.AdapterErrorBadServerResponse: s, - pbsmetrics.AdapterErrorUnknown: s, - }, - }, - { - Source: pbsmetrics.DemandApp, - RType: pbsmetrics.ReqTypeORTB2Web, - Adapter: openrtb_ext.BidderIx, - PubID: "Pub2", - Browser: pbsmetrics.BrowserOther, - CookieFlag: pbsmetrics.CookieFlagNo, - AdapterBids: pbsmetrics.AdapterBidPresent, - AdapterErrors: map[pbsmetrics.AdapterError]struct{}{}, - }, - { - Source: pbsmetrics.DemandUnknown, - RType: pbsmetrics.ReqTypeORTB2App, - Adapter: openrtb_ext.BidderAppnexus, - PubID: "Pub3", - Browser: pbsmetrics.BrowserOther, - CookieFlag: pbsmetrics.CookieFlagUnknown, - AdapterBids: pbsmetrics.AdapterBidPresent, - AdapterErrors: map[pbsmetrics.AdapterError]struct{}{}, - }, - { - Source: pbsmetrics.DemandWeb, - RType: pbsmetrics.ReqTypeVideo, - Adapter: openrtb_ext.BidderAppnexus, - PubID: "Pub4", - Browser: pbsmetrics.BrowserOther, - CookieFlag: pbsmetrics.CookieFlagUnknown, - AdapterBids: pbsmetrics.AdapterBidPresent, - AdapterErrors: map[pbsmetrics.AdapterError]struct{}{}, - }, +func TestCookieMetric(t *testing.T) { + m := createMetricsForTesting() + + m.RecordCookieSync() + + expectedCount := float64(1) + assertCounterValue(t, "", "cookieSync", m.cookieSync, + expectedCount) +} + +func TestPrebidCacheRequestTimeMetric(t *testing.T) { + m := createMetricsForTesting() + + m.RecordPrebidCacheRequestTime(true, time.Duration(100)*time.Millisecond) + m.RecordPrebidCacheRequestTime(false, time.Duration(200)*time.Millisecond) + + successExpectedCount := uint64(1) + successExpectedSum := float64(0.1) + successResult := getHistogramFromHistogramVec(m.prebidCacheWriteTimer, successLabel, "true") + assertHistogram(t, "Success", successResult, successExpectedCount, successExpectedSum) + + errorExpectedCount := uint64(1) + errorExpectedSum := float64(0.2) + errorResult := getHistogramFromHistogramVec(m.prebidCacheWriteTimer, successLabel, "false") + assertHistogram(t, "Error", errorResult, errorExpectedCount, errorExpectedSum) } -var userLabels = []pbsmetrics.UserLabels{ - { - Action: pbsmetrics.RequestActionSet, - Bidder: openrtb_ext.BidderAppnexus, - }, - { - Action: pbsmetrics.RequestActionGDPR, - Bidder: openrtb_ext.BidderAppnexus, - }, - { - Action: pbsmetrics.RequestActionSet, - Bidder: openrtb_ext.BidderRubicon, - }, - { - Action: pbsmetrics.RequestActionOptOut, - Bidder: openrtb_ext.BidderOpenx, - }, +func TestMetricAccumulationSpotCheck(t *testing.T) { + m := createMetricsForTesting() + + m.RecordLegacyImps(pbsmetrics.Labels{}, 1) + m.RecordLegacyImps(pbsmetrics.Labels{}, 2) + m.RecordLegacyImps(pbsmetrics.Labels{}, 3) + + expectedValue := float64(1 + 2 + 3) + assertCounterValue(t, "", "impressionsLegacy", m.impressionsLegacy, + expectedValue) } -func assertMetricValue(t *testing.T, name string, m *dto.Metric, expected string) { - v := m.String() - if v != expected { - t.Errorf("Bad value for metric %s: expected=\"%s\", found=\"%s\"", name, expected, v) - } +func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) { + m := dto.Metric{} + counter.Write(&m) + actual := *m.GetCounter().Value + + assert.Equal(t, expected, actual, description) } -func assertGaugeValue(t *testing.T, name string, m *dto.Metric, expected int) { - v, err := strconv.Atoi(gaugeValueRegexp.FindStringSubmatch(m.String())[1]) - if err != nil { - t.Errorf("Could not extract the value for metric %s. (output was %s, error was %v)", name, m.String(), err) - } - if v != expected { - t.Errorf("Bad value for metric %s: expected=\"%d\", found=\"%d\"", name, expected, v) - } +func assertCounterVecValue(t *testing.T, description, name string, counterVec *prometheus.CounterVec, expected float64, labels prometheus.Labels) { + counter := counterVec.With(labels) + assertCounterValue(t, description, name, counter, expected) } -func assertCounterValue(t *testing.T, name string, m *dto.Metric, expected int) { - v, err := strconv.Atoi(counterValueRegexp.FindStringSubmatch(m.String())[1]) - if err != nil { - t.Errorf("Could not extract the value for metric %s. (output was %s, error was %v)", name, m.String(), err) - } - if v != expected { - t.Errorf("Bad value for metric %s: expected=\"%d\", found=\"%d\"", name, expected, v) - } +func getHistogramFromHistogramVec(histogram *prometheus.HistogramVec, labelKey, labelValue string) dto.Histogram { + var result dto.Histogram + processMetrics(histogram, func(m dto.Metric) { + for _, label := range m.GetLabel() { + if label.GetName() == labelKey && label.GetValue() == labelValue { + result = *m.GetHistogram() + } + } + }) + return result } -func assertHistogramValue(t *testing.T, name string, m *dto.Metric, expected int) { - v, err := strconv.Atoi(histogramValueRegexp.FindStringSubmatch(m.String())[1]) - if err != nil { - t.Errorf("Could not extract the value for metric %s. (output was %s, error was %v)", name, m.String(), err) - } - if v != expected { - t.Errorf("Bad value for metric %s: expected=\"%d\", found=\"%d\"", name, expected, v) +func processMetrics(collector prometheus.Collector, handler func(m dto.Metric)) { + collectorChan := make(chan prometheus.Metric) + go func() { + collector.Collect(collectorChan) + close(collectorChan) + }() + + for metric := range collectorChan { + dtoMetric := dto.Metric{} + metric.Write(&dtoMetric) + handler(dtoMetric) } } + +func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expectedCount uint64, expectedSum float64) { + assert.Equal(t, expectedCount, histogram.GetSampleCount(), name+":count") + assert.Equal(t, expectedSum, histogram.GetSampleSum(), name+":sum") +} diff --git a/pbsmetrics/prometheus/type_conversion.go b/pbsmetrics/prometheus/type_conversion.go new file mode 100644 index 00000000000..8294ede0617 --- /dev/null +++ b/pbsmetrics/prometheus/type_conversion.go @@ -0,0 +1,78 @@ +package prometheusmetrics + +import ( + "strconv" + + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbsmetrics" +) + +func actionsAsString() []string { + values := pbsmetrics.RequestActions() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func adaptersAsString() []string { + values := openrtb_ext.BidderList() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func adapterErrorsAsString() []string { + values := pbsmetrics.AdapterErrors() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func boolValuesAsString() []string { + return []string{ + strconv.FormatBool(true), + strconv.FormatBool(false), + } +} + +func cookieTypesAsString() []string { + values := pbsmetrics.CookieTypes() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func cacheResultsAsString() []string { + values := pbsmetrics.CacheResults() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func requestStatusesAsString() []string { + values := pbsmetrics.RequestStatuses() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func requestTypesAsString() []string { + values := pbsmetrics.RequestTypes() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 10acd363553..88da5c3ac0d 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -109,14 +109,14 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s anResp, err := ctxhttp.Do(ctx, c.httpClient, httpReq) elapsedTime := time.Since(startTime) if err != nil { - c.metrics.RecordPrebidCacheRequestTime(pbsmetrics.RequestLabels{RequestStatus: pbsmetrics.RequestStatusErr}, elapsedTime) + c.metrics.RecordPrebidCacheRequestTime(false, elapsedTime) friendlyErr := fmt.Errorf("Error sending the request to Prebid Cache: %v; Duration=%v", err, elapsedTime) glog.Error(friendlyErr) errs = append(errs, friendlyErr) return uuidsToReturn, errs } defer anResp.Body.Close() - c.metrics.RecordPrebidCacheRequestTime(pbsmetrics.RequestLabels{RequestStatus: pbsmetrics.RequestStatusOK}, elapsedTime) + c.metrics.RecordPrebidCacheRequestTime(true, elapsedTime) responseBody, err := ioutil.ReadAll(anResp.Body) if anResp.StatusCode != 200 { diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index 535d503f094..cbeaa38a105 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -48,10 +48,7 @@ func TestBadResponse(t *testing.T) { defer server.Close() metricsMock := &pbsmetrics.MetricsEngineMock{} - successCacheCallResponseLabels := pbsmetrics.RequestLabels{ - RequestStatus: pbsmetrics.RequestStatusOK, - } - metricsMock.On("RecordPrebidCacheRequestTime", successCacheCallResponseLabels, mock.Anything).Once() + metricsMock.On("RecordPrebidCacheRequestTime", true, mock.Anything).Once() client := &clientImpl{ httpClient: server.Client(), @@ -82,10 +79,7 @@ func TestCancelledContext(t *testing.T) { defer server.Close() metricsMock := &pbsmetrics.MetricsEngineMock{} - errorCacheCallResponseLabels := pbsmetrics.RequestLabels{ - RequestStatus: pbsmetrics.RequestStatusErr, - } - metricsMock.On("RecordPrebidCacheRequestTime", errorCacheCallResponseLabels, mock.Anything).Once() + metricsMock.On("RecordPrebidCacheRequestTime", false, mock.Anything).Once() client := &clientImpl{ httpClient: server.Client(), @@ -111,10 +105,7 @@ func TestSuccessfulPut(t *testing.T) { defer server.Close() metricsMock := &pbsmetrics.MetricsEngineMock{} - successCacheCallResponseLabels := pbsmetrics.RequestLabels{ - RequestStatus: pbsmetrics.RequestStatusOK, - } - metricsMock.On("RecordPrebidCacheRequestTime", successCacheCallResponseLabels, mock.Anything).Once() + metricsMock.On("RecordPrebidCacheRequestTime", true, mock.Anything).Once() client := &clientImpl{ httpClient: server.Client(),