Repository: prebid/prebid-server
Pull request #1226 (2020-03-18 17:56:43)

Added metrics support to endpoint aspect

VeronikaSolovei9: Ready for review
VeronikaSolovei9: Ready for review
VeronikaSolovei9: Previous commits squashed and rebased over latest upstream.

Patch:
diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go
index 46caf262..dad85ee1 100644
--- a/adapters/rubicon/rubicon.go
+++ b/adapters/rubicon/rubicon.go
@@ -21,6 +21,8 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
+const badvLimitSize = 50
+
 type RubiconAdapter struct {
 	http         *adapters.HTTPAdapter
 	URI          string
@@ -740,6 +742,13 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap
 			request.App = &appCopy
 		}
 
+		reqBadv := request.BAdv
+		if reqBadv != nil {
+			if len(reqBadv) > badvLimitSize {
+				request.BAdv = reqBadv[:badvLimitSize]
+			}
+		}
+
 		request.Imp = []openrtb.Imp{thisImp}
 		request.Cur = nil
 		request.Ext = nil
diff --git a/docs/bidders/kidoz.md b/docs/bidders/kidoz.md
new file mode 100644
index 00000000..433dd71c
--- /dev/null
+++ b/docs/bidders/kidoz.md
@@ -0,0 +1,9 @@
+# Kidoz Bidder
+
+Kidoz is exclusively for Mobile app COPPA compatible ads, 100% kid relevant and appropriate.
+
+In order for a company to receive bids from Kidoz, they must first open a publisher account at Kidoz.net
+(https://accounts.kidoz.net/publishers/register) and accept the Kidoz Terms and Conditions and Privacy Policy. 
+Kidoz publishers must confirm that all of their content properties are COPPA and GDPR compliant and perform no monitoring
+or tracking of U13 users in their operations.  New publishers are provided a Publisher ID and AccessToken, this can also
+be used to login to their dashboard at the Kidoz.net portal to monitor their account activity. 
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 3cab1880..e625e5ca 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -179,6 +179,12 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 					errs = append(errs, errors.New("Unable to marshal response ext for debugging"))
 				}
 			}
+
+			if requestExt.Prebid.SupportDeals {
+				dealErrs := applyDealSupport(bidRequest, auc, bidCategory)
+				errs = append(errs, dealErrs...)
+			}
+
 			cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &e.defaultTTLs, bidCategory, debugLog)
 			if len(cacheErrs) > 0 {
 				errs = append(errs, cacheErrs...)
@@ -186,10 +192,6 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 			targData.setTargeting(auc, bidRequest.App != nil, bidCategory)
 		}
 
-		if requestExt.Prebid.SupportDeals {
-			dealErrs := applyDealSupport(bidRequest, auc)
-			errs = append(errs, dealErrs...)
-		}
 	}
 
 	// Build the response
@@ -210,7 +212,7 @@ type BidderDealTier struct {
 }
 
 // applyDealSupport updates targeting keys with deal prefixes if minimum deal tier exceeded
-func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction) []error {
+func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction, bidCategory map[string]string) []error {
 	errs := []error{}
 	impDealMap := getDealTiers(bidRequest)
 
@@ -221,7 +223,7 @@ func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction) []error {
 
 			if topBidPerBidder.dealPriority > 0 {
 				if validateAndNormalizeDealTier(impDeal[bidderString]) {
-					updateHbPbCatDur(topBidPerBidder, impDeal[bidderString].Info)
+					updateHbPbCatDur(topBidPerBidder, impDeal[bidderString].Info, bidCategory)
 				} else {
 					errs = append(errs, fmt.Errorf("dealTier configuration invalid for bidder '%s', imp ID '%s'", bidderString, impID))
 				}
@@ -258,16 +260,16 @@ func validateAndNormalizeDealTier(impDeal *DealTier) bool {
 	return len(impDeal.Info.Prefix) > 0 && impDeal.Info.MinDealTier > 0
 }
 
-func updateHbPbCatDur(bid *pbsOrtbBid, dealTierInfo *DealTierInfo) {
+func updateHbPbCatDur(bid *pbsOrtbBid, dealTierInfo *DealTierInfo, bidCategory map[string]string) {
 	if bid.dealPriority >= dealTierInfo.MinDealTier {
 		prefixTier := fmt.Sprintf("%s%d_", dealTierInfo.Prefix, bid.dealPriority)
 
-		if oldCatDur, ok := bid.bidTargets["hb_pb_cat_dur"]; ok {
+		if oldCatDur, ok := bidCategory[bid.bid.ID]; ok {
 			oldCatDurSplit := strings.SplitAfterN(oldCatDur, "_", 2)
 			oldCatDurSplit[0] = prefixTier
 
 			newCatDur := strings.Join(oldCatDurSplit, "")
-			bid.bidTargets["hb_pb_cat_dur"] = newCatDur
+			bidCategory[bid.bid.ID] = newCatDur
 		}
 	}
 }
diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go
index 81cfbfd0..e1cdaceb 100644
--- a/pbsmetrics/config/metrics.go
+++ b/pbsmetrics/config/metrics.go
@@ -181,6 +181,13 @@ func (me *MultiMetricsEngine) RecordPrebidCacheRequestTime(success bool, length
 	}
 }
 
+// RecordRequestQueueTime across all engines
+func (me *MultiMetricsEngine) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) {
+	for _, thisME := range *me {
+		thisME.RecordRequestQueueTime(success, requestType, length)
+	}
+}
+
 // DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests)
 type DummyMetricsEngine struct{}
 
@@ -251,3 +258,7 @@ func (me *DummyMetricsEngine) RecordStoredImpCacheResult(cacheResult pbsmetrics.
 // RecordPrebidCacheRequestTime as a noop
 func (me *DummyMetricsEngine) RecordPrebidCacheRequestTime(success bool, length time.Duration) {
 }
+
+// RecordRequestQueueTime as a noop
+func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) {
+}
diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go
index 9b3dd65f..ff3d9681 100644
--- a/pbsmetrics/go_metrics.go
+++ b/pbsmetrics/go_metrics.go
@@ -24,6 +24,7 @@ type Metrics struct {
 	SafariRequestMeter             metrics.Meter
 	SafariNoCookieMeter            metrics.Meter
 	RequestTimer                   metrics.Timer
+	RequestsQueueTimer             map[RequestType]map[bool]metrics.Timer
 	PrebidCacheRequestTimerSuccess metrics.Timer
 	PrebidCacheRequestTimerError   metrics.Timer
 	StoredReqCacheMeter            map[CacheResult]metrics.Meter
@@ -111,6 +112,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 		SafariRequestMeter:             blankMeter,
 		SafariNoCookieMeter:            blankMeter,
 		RequestTimer:                   blankTimer,
+		RequestsQueueTimer:             make(map[RequestType]map[bool]metrics.Timer),
 		PrebidCacheRequestTimerSuccess: blankTimer,
 		PrebidCacheRequestTimerError:   blankTimer,
 		StoredReqCacheMeter:            make(map[CacheResult]metrics.Meter),
@@ -146,6 +148,11 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 		}
 	}
 
+	//to minimize memory usage, queuedTimeout metric is now supported for video endpoint only
+	//boolean value represents 2 general request statuses: accepted and rejected
+	newMetrics.RequestsQueueTimer["video"] = make(map[bool]metrics.Timer)
+	newMetrics.RequestsQueueTimer["video"][true] = blankTimer
+	newMetrics.RequestsQueueTimer["video"][false] = blankTimer
 	return newMetrics
 }
 
@@ -191,11 +198,15 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
 			statusMap[stat] = metrics.GetOrRegisterMeter("requests."+string(stat)+"."+string(typ), registry)
 		}
 	}
+
 	for _, cacheRes := range CacheResults() {
 		newMetrics.StoredReqCacheMeter[cacheRes] = metrics.GetOrRegisterMeter(fmt.Sprintf("stored_request_cache_%s", string(cacheRes)), registry)
 		newMetrics.StoredImpCacheMeter[cacheRes] = metrics.GetOrRegisterMeter(fmt.Sprintf("stored_imp_cache_%s", string(cacheRes)), registry)
 	}
 
+	newMetrics.RequestsQueueTimer["video"][true] = metrics.GetOrRegisterTimer("queued_requests.video.accepted", registry)
+	newMetrics.RequestsQueueTimer["video"][false] = metrics.GetOrRegisterTimer("queued_requests.video.rejected", registry)
+
 	newMetrics.userSyncSet[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.sets", registry)
 	newMetrics.userSyncGDPRPrevent[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.gdpr_prevent", registry)
 	return newMetrics
@@ -526,6 +537,13 @@ func (me *Metrics) RecordPrebidCacheRequestTime(success bool, length time.Durati
 	}
 }
 
+func (me *Metrics) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) {
+	if requestType == ReqTypeVideo { //remove this check when other request types are supported
+		me.RequestsQueueTimer[requestType][success].Update(length)
+	}
+
+}
+
 func doMark(bidder openrtb_ext.BidderName, meters map[openrtb_ext.BidderName]metrics.Meter) {
 	met, ok := meters[bidder]
 	if ok {
diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go
index cc836011..611692c9 100644
--- a/pbsmetrics/metrics.go
+++ b/pbsmetrics/metrics.go
@@ -154,11 +154,12 @@ func CookieTypes() []CookieFlag {
 
 // Request/return status
 const (
-	RequestStatusOK          RequestStatus = "ok"
-	RequestStatusBadInput    RequestStatus = "badinput"
-	RequestStatusErr         RequestStatus = "err"
-	RequestStatusNetworkErr  RequestStatus = "networkerr"
-	RequestStatusBlacklisted RequestStatus = "blacklistedacctorapp"
+	RequestStatusOK           RequestStatus = "ok"
+	RequestStatusBadInput     RequestStatus = "badinput"
+	RequestStatusErr          RequestStatus = "err"
+	RequestStatusNetworkErr   RequestStatus = "networkerr"
+	RequestStatusBlacklisted  RequestStatus = "blacklistedacctorapp"
+	RequestStatusQueueTimeout RequestStatus = "queuetimeout"
 )
 
 func RequestStatuses() []RequestStatus {
@@ -168,6 +169,7 @@ func RequestStatuses() []RequestStatus {
 		RequestStatusErr,
 		RequestStatusNetworkErr,
 		RequestStatusBlacklisted,
+		RequestStatusQueueTimeout,
 	}
 }
 
@@ -272,4 +274,5 @@ type MetricsEngine interface {
 	RecordStoredReqCacheResult(cacheResult CacheResult, inc int)
 	RecordStoredImpCacheResult(cacheResult CacheResult, inc int)
 	RecordPrebidCacheRequestTime(success bool, length time.Duration)
+	RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
 }
diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go
index 6d57f9fc..1f5b84b1 100644
--- a/pbsmetrics/metrics_mock.go
+++ b/pbsmetrics/metrics_mock.go
@@ -96,3 +96,8 @@ func (me *MetricsEngineMock) RecordStoredImpCacheResult(cacheResult CacheResult,
 func (me *MetricsEngineMock) RecordPrebidCacheRequestTime(success bool, length time.Duration) {
 	me.Called(success, length)
 }
+
+// RecordRequestQueueTime mock
+func (me *MetricsEngineMock) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) {
+	me.Called(success, requestType, length)
+}
diff --git a/pbsmetrics/prometheus/preload.go b/pbsmetrics/prometheus/preload.go
index 7654dd54..11e6bdc1 100644
--- a/pbsmetrics/prometheus/preload.go
+++ b/pbsmetrics/prometheus/preload.go
@@ -1,6 +1,7 @@
 package prometheusmetrics
 
 import (
+	"github.com/prebid/prebid-server/pbsmetrics"
 	"github.com/prometheus/client_golang/prometheus"
 )
 
@@ -91,6 +92,13 @@ func preloadLabelValues(m *Metrics) {
 		adapterLabel: adapterValues,
 		actionLabel:  actionValues,
 	})
+
+	//to minimize memory usage, queuedTimeout metric is now supported for video endpoint only
+	//boolean value represents 2 general request statuses: accepted and rejected
+	preloadLabelValuesForHistogram(m.requestsQueueTimer, map[string][]string{
+		requestTypeLabel:   {string(pbsmetrics.ReqTypeVideo)},
+		requestStatusLabel: {requestSuccessLabel, requestRejectLabel},
+	})
 }
 
 func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) {
diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go
index e2b646d5..d66defea 100644
--- a/pbsmetrics/prometheus/prometheus.go
+++ b/pbsmetrics/prometheus/prometheus.go
@@ -24,6 +24,7 @@ type Metrics struct {
 	prebidCacheWriteTimer        *prometheus.HistogramVec
 	requests                     *prometheus.CounterVec
 	requestsTimer                *prometheus.HistogramVec
+	requestsQueueTimer           *prometheus.HistogramVec
 	requestsWithoutCookie        *prometheus.CounterVec
 	storedImpressionsCacheResult *prometheus.CounterVec
 	storedRequestCacheResult     *prometheus.CounterVec
@@ -73,11 +74,17 @@ const (
 	markupDeliveryNurl = "nurl"
 )
 
+const (
+	requestSuccessLabel = "requestAcceptedLabel"
+	requestRejectLabel  = "requestRejectedLabel"
+)
+
 // 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}
 	cacheWriteTimeBuckets := []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}
+	queuedRequestTimeBuckets := []float64{0, 1, 5, 30, 60, 120, 180, 240, 300}
 
 	metrics := Metrics{}
 	metrics.Registry = prometheus.NewRegistry()
@@ -187,6 +194,12 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 		"Count of total requests to Prebid Server labeled by account.",
 		[]string{accountLabel})
 
+	metrics.requestsQueueTimer = newHistogram(cfg, metrics.Registry,
+		"request_queue_time",
+		"Seconds request was waiting in queue",
+		[]string{requestTypeLabel, requestStatusLabel},
+		queuedRequestTimeBuckets)
+
 	preloadLabelValues(&metrics)
 
 	return &metrics
@@ -374,3 +387,14 @@ func (m *Metrics) RecordPrebidCacheRequestTime(success bool, length time.Duratio
 		successLabel: strconv.FormatBool(success),
 	}).Observe(length.Seconds())
 }
+
+func (m *Metrics) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) {
+	successLabelFormatted := requestRejectLabel
+	if success {
+		successLabelFormatted = requestSuccessLabel
+	}
+	m.requestsQueueTimer.With(prometheus.Labels{
+		requestTypeLabel:   string(requestType),
+		requestStatusLabel: successLabelFormatted,
+	}).Observe(length.Seconds())
+}
diff --git a/router/aspects/request_timeout_handler.go b/router/aspects/request_timeout_handler.go
index ae11f8c5..23d6cef9 100644
--- a/router/aspects/request_timeout_handler.go
+++ b/router/aspects/request_timeout_handler.go
@@ -3,11 +3,13 @@ package aspects
 import (
 	"github.com/julienschmidt/httprouter"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/pbsmetrics"
 	"net/http"
 	"strconv"
+	"time"
 )
 
-func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders) httprouter.Handle {
+func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders, metricsEngine pbsmetrics.MetricsEngine, requestType pbsmetrics.RequestType) httprouter.Handle {
 
 	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
 
@@ -30,13 +32,17 @@ func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestT
 			return
 		}
 
+		reqTimeDuration := time.Duration(reqTimeFloat * float64(time.Second))
+
 		//Return HTTP 408 if requests stays too long in queue
 		if reqTimeFloat >= reqTimeoutFloat {
 			w.WriteHeader(http.StatusRequestTimeout)
 			w.Write([]byte("Queued request processing time exceeded maximum"))
+			metricsEngine.RecordRequestQueueTime(false, requestType, reqTimeDuration)
 			return
 		}
 
+		metricsEngine.RecordRequestQueueTime(true, requestType, reqTimeDuration)
 		f(w, r, params)
 	}
 
diff --git a/router/router.go b/router/router.go
index 8ac463b8..68627f93 100644
--- a/router/router.go
+++ b/router/router.go
@@ -6,6 +6,7 @@ import (
 	"database/sql"
 	"encoding/json"
 	"fmt"
+	"github.com/prebid/prebid-server/pbsmetrics"
 	"io/ioutil"
 	"net/http"
 	"path/filepath"
@@ -258,7 +259,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r
 
 	requestTimeoutHeaders := config.RequestTimeoutHeaders{}
 	if cfg.RequestTimeoutHeaders != requestTimeoutHeaders {
-		videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders)
+		videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, pbsmetrics.ReqTypeVideo)
 	}
 
 	r.POST("/auction", endpoints.Auction(cfg, syncers, gdprPerms, r.MetricsEngine, dataCache, exchanges))
diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml
index f0a44470..84ba6d68 100644
--- a/static/bidder-info/33across.yaml
+++ b/static/bidder-info/33across.yaml
@@ -1,9 +1,9 @@
 maintainer:
-  email: "dev@33across.com"
+  email: "headerbidding@33across.com"
 capabilities:
   app:
     mediaTypes:
     - banner
   site:
     mediaTypes:
-    - banner
\ No newline at end of file
+    - banner
diff --git a/static/bidder-info/adoppler.yaml b/static/bidder-info/adoppler.yaml
index 7fa79eda..1b101039 100644
--- a/static/bidder-info/adoppler.yaml
+++ b/static/bidder-info/adoppler.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: info@adoppler.com
+  email: pbs@adoppler.com
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/advangelists.yaml b/static/bidder-info/advangelists.yaml
index e1bc6c0a..aed9900d 100644
--- a/static/bidder-info/advangelists.yaml
+++ b/static/bidder-info/advangelists.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "lokesh@advangelists.com"
+  email: "prebid@advangelists.com"
 capabilities:
   site:
     mediaTypes:
diff --git a/static/bidder-info/appnexus.yaml b/static/bidder-info/appnexus.yaml
index 585c59b9..f1e7ca23 100644
--- a/static/bidder-info/appnexus.yaml
+++ b/static/bidder-info/appnexus.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "info@prebid.org"
+  email: "prebid-server@xandr.com"
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/audienceNetwork.yaml b/static/bidder-info/audienceNetwork.yaml
index 34700f2f..56230bf3 100644
--- a/static/bidder-info/audienceNetwork.yaml
+++ b/static/bidder-info/audienceNetwork.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "info@prebid.org"
+  email: "none"
 capabilities:
   site:
     mediaTypes:
diff --git a/static/bidder-info/brightroll.yaml b/static/bidder-info/brightroll.yaml
index 14d9a45f..f913be6d 100644
--- a/static/bidder-info/brightroll.yaml
+++ b/static/bidder-info/brightroll.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "smithaa@oath.com"
+  email: "dsp-supply-prebid@verizonmedia.com"
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/conversant.yaml b/static/bidder-info/conversant.yaml
index ce67700e..017f0e0c 100644
--- a/static/bidder-info/conversant.yaml
+++ b/static/bidder-info/conversant.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "mediapsr@conversantmedia.com"
+  email: "CNVR_PublisherIntegration@conversantmedia.com"
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/datablocks.yaml b/static/bidder-info/datablocks.yaml
index 9bf7e780..43f00a63 100644
--- a/static/bidder-info/datablocks.yaml
+++ b/static/bidder-info/datablocks.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "henry@datablocks.net"
+  email: "prebid@datablocks.net"
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/engagebdr.yaml b/static/bidder-info/engagebdr.yaml
index d2f74762..57c359e4 100644
--- a/static/bidder-info/engagebdr.yaml
+++ b/static/bidder-info/engagebdr.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "admin@engagebdr.com"
+  email: "tech@engagebdr.com"
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/gamoshi.yaml b/static/bidder-info/gamoshi.yaml
index 71120ed0..c3ed3ff1 100644
--- a/static/bidder-info/gamoshi.yaml
+++ b/static/bidder-info/gamoshi.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "moses@gamoshi.com"
+  email: "dev@gamoshi.com"
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml
index b8a3981c..0feca7cd 100644
--- a/static/bidder-info/gumgum.yaml
+++ b/static/bidder-info/gumgum.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "pubtech@gumgum.com"
+  email: "prebid@gumgum.com"
 capabilities:
   site:
     mediaTypes:
diff --git a/static/bidder-info/ix.yaml b/static/bidder-info/ix.yaml
index ff29ec03..326989ae 100644
--- a/static/bidder-info/ix.yaml
+++ b/static/bidder-info/ix.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "info@prebid.org"
+  email: "pdu-supply-prebid@indexexchange.com"
 capabilities:
   site:
     mediaTypes:
diff --git a/static/bidder-info/openx.yaml b/static/bidder-info/openx.yaml
index ce2b67db..e3062b54 100644
--- a/static/bidder-info/openx.yaml
+++ b/static/bidder-info/openx.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "team-openx@openx.com"
+  email: "prebid@openx.com"
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml
index b9fd3242..716e4530 100644
--- a/static/bidder-info/pulsepoint.yaml
+++ b/static/bidder-info/pulsepoint.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "info@prebid.org"
+  email: "ExchangeTeam@pulsepoint.com"
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml
index 4b899eb3..f15af6ca 100644
--- a/static/bidder-info/rtbhouse.yaml
+++ b/static/bidder-info/rtbhouse.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "inventory.devel@rtbhouse.com"
+  email: "prebid@rtbhouse.com"
 capabilities:
   site:
     mediaTypes:
diff --git a/static/bidder-info/sonobi.yaml b/static/bidder-info/sonobi.yaml
index f49fa281..6d39319a 100644
--- a/static/bidder-info/sonobi.yaml
+++ b/static/bidder-info/sonobi.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "apex@sonobi.com"
+  email: "apex.prebid@sonobi.com"
 capabilities:
   site:
     mediaTypes:
diff --git a/static/bidder-info/verizonmedia.yaml b/static/bidder-info/verizonmedia.yaml
index da5725ee..024cafad 100644
--- a/static/bidder-info/verizonmedia.yaml
+++ b/static/bidder-info/verizonmedia.yaml
@@ -1,6 +1,6 @@
 maintainer:
-  email: "hb-fe-tech@verizonmedia.com"
+  email: "dsp-supply-prebid@verizonmedia.com"
 capabilities:
   site:
     mediaTypes:
-      - banner
\ No newline at end of file
+      - banner
diff --git a/static/bidder-info/visx.yaml b/static/bidder-info/visx.yaml
index f404a013..b6a16e4c 100644
--- a/static/bidder-info/visx.yaml
+++ b/static/bidder-info/visx.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "service@yoc.com"
+  email: "supply.partners@yoc.com"
 capabilities:
   site:
     mediaTypes:
diff --git a/static/bidder-info/yieldmo.yaml b/static/bidder-info/yieldmo.yaml
index 7d6c0af6..514f1745 100644
--- a/static/bidder-info/yieldmo.yaml
+++ b/static/bidder-info/yieldmo.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "progsupport@yieldmo.com"
+  email: "prebid@yieldmo.com"
 capabilities:
   site:
     mediaTypes:
diff --git a/static/bidder-info/zeroclickfraud.yaml b/static/bidder-info/zeroclickfraud.yaml
index 9bf7e780..527c0065 100644
--- a/static/bidder-info/zeroclickfraud.yaml
+++ b/static/bidder-info/zeroclickfraud.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "henry@datablocks.net"
+  email: "support@datablocks.net"
 capabilities:
   app:
     mediaTypes:


Test patch:
diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go
index d386daed..96623659 100644
--- a/adapters/rubicon/rubicon_test.go
+++ b/adapters/rubicon/rubicon_test.go
@@ -7,6 +7,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"strconv"
 	"testing"
 	"time"
 
@@ -1133,6 +1134,48 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) {
 		"Unexpected dfp_ad_unit_code: %s", rubiconExtInventory["dfp_ad_unit_code"])
 }
 
+func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) {
+	SIZE_ID := getTestSizes()
+	bidder := new(RubiconAdapter)
+
+	badvOverflowed := make([]string, 100)
+	for i := range badvOverflowed {
+		badvOverflowed[i] = strconv.Itoa(i)
+	}
+
+	request := &openrtb.BidRequest{
+		ID:   "test-request-id",
+		BAdv: badvOverflowed,
+		Imp: []openrtb.Imp{{
+			ID: "test-imp-id",
+			Banner: &openrtb.Banner{
+				Format: []openrtb.Format{
+					SIZE_ID[15],
+				},
+			},
+			Ext: json.RawMessage(`{
+				"bidder": {
+					"zoneId": 8394,
+					"siteId": 283282,
+					"accountId": 7891,
+					"inventory": {"key1" : "val1"},
+					"visitor": {"key2" : "val2"}
+				}
+			}`),
+		}},
+	}
+
+	reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{})
+
+	rubiconReq := &openrtb.BidRequest{}
+	if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil {
+		t.Fatalf("Unexpected error while decoding request: %s", err)
+	}
+
+	badvRequest := rubiconReq.BAdv
+	assert.Equal(t, badvOverflowed[:50], badvRequest, "Unexpected dfp_ad_unit_code: %s")
+}
+
 func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) {
 	SIZE_ID := getTestSizes()
 	bidder := new(RubiconAdapter)
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 2f115ca4..f263eea8 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -1403,7 +1403,10 @@ func TestApplyDealSupport(t *testing.T) {
 			},
 		}
 
-		bid := pbsOrtbBid{&openrtb.Bid{}, "video", test.targ, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority}
+		bid := pbsOrtbBid{&openrtb.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority}
+		bidCategory := map[string]string{
+			bid.bid.ID: test.targ["hb_pb_cat_dur"],
+		}
 
 		auc := &auction{
 			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
@@ -1413,9 +1416,9 @@ func TestApplyDealSupport(t *testing.T) {
 			},
 		}
 
-		dealErrs := applyDealSupport(bidRequest, auc)
+		dealErrs := applyDealSupport(bidRequest, auc, bidCategory)
 
-		assert.Equal(t, test.expectedHbPbCatDur, auc.winningBidsByBidder["imp_id1"][bidderName].bidTargets["hb_pb_cat_dur"], test.description)
+		assert.Equal(t, test.expectedHbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][bidderName].bid.ID], test.description)
 		if len(test.expectedDealErr) > 0 {
 			assert.Containsf(t, dealErrs, errors.New(test.expectedDealErr), "Expected error message not found in deal errors")
 		}
@@ -1590,11 +1593,14 @@ func TestUpdateHbPbCatDur(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		bid := pbsOrtbBid{&openrtb.Bid{}, "video", test.targ, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority}
+		bid := pbsOrtbBid{&openrtb.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, test.dealPriority}
+		bidCategory := map[string]string{
+			bid.bid.ID: test.targ["hb_pb_cat_dur"],
+		}
 
-		updateHbPbCatDur(&bid, test.dealTier)
+		updateHbPbCatDur(&bid, test.dealTier, bidCategory)
 
-		assert.Equal(t, test.expectedHbPbCatDur, bid.bidTargets["hb_pb_cat_dur"], test.description)
+		assert.Equal(t, test.expectedHbPbCatDur, bidCategory[bid.bid.ID], test.description)
 	}
 }
 
diff --git a/pbsmetrics/config/metrics_test.go b/pbsmetrics/config/metrics_test.go
index ad817ba7..d2374f95 100644
--- a/pbsmetrics/config/metrics_test.go
+++ b/pbsmetrics/config/metrics_test.go
@@ -115,6 +115,9 @@ func TestMultiMetricsEngine(t *testing.T) {
 	for i := 0; i < 3; i++ {
 		metricsEngine.RecordImps(impTypeLabels)
 	}
+
+	metricsEngine.RecordRequestQueueTime(false, pbsmetrics.ReqTypeVideo, time.Duration(1))
+
 	//Make the metrics engine, instantiated here with goEngine, fill its RequestStatuses[RequestType][pbsmetrics.RequestStatusXX] with the new boolean values added to pbsmetrics.Labels
 	VerifyMetrics(t, "RequestStatuses.OpenRTB2.OK", goEngine.RequestStatuses[pbsmetrics.ReqTypeORTB2Web][pbsmetrics.RequestStatusOK].Count(), 5)
 	VerifyMetrics(t, "RequestStatuses.Legacy.OK", goEngine.RequestStatuses[pbsmetrics.ReqTypeLegacy][pbsmetrics.RequestStatusOK].Count(), 0)
@@ -148,6 +151,9 @@ func TestMultiMetricsEngine(t *testing.T) {
 	}
 	VerifyMetrics(t, "AdapterMetrics.AppNexus.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GotBidsMeter.Count(), 0)
 	VerifyMetrics(t, "AdapterMetrics.AppNexus.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].NoBidMeter.Count(), 5)
+
+	VerifyMetrics(t, "RecordRequestQueueTime.Video.Rejected", goEngine.RequestsQueueTimer[pbsmetrics.ReqTypeVideo][false].Count(), 1)
+	VerifyMetrics(t, "RecordRequestQueueTime.Video.Accepted", goEngine.RequestsQueueTimer[pbsmetrics.ReqTypeVideo][true].Count(), 0)
 }
 
 func VerifyMetrics(t *testing.T, name string, actual int64, expected int64) {
diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go
index b403733d..253ff69e 100644
--- a/pbsmetrics/go_metrics_test.go
+++ b/pbsmetrics/go_metrics_test.go
@@ -50,6 +50,9 @@ func TestNewMetrics(t *testing.T) {
 	ensureContains(t, registry, "requests.badinput.video", m.RequestStatuses[ReqTypeVideo][RequestStatusBadInput])
 	ensureContains(t, registry, "requests.err.video", m.RequestStatuses[ReqTypeVideo][RequestStatusErr])
 	ensureContains(t, registry, "requests.networkerr.video", m.RequestStatuses[ReqTypeVideo][RequestStatusNetworkErr])
+
+	ensureContains(t, registry, "queued_requests.video.rejected", m.RequestsQueueTimer[ReqTypeVideo][false])
+	ensureContains(t, registry, "queued_requests.video.accepted", m.RequestsQueueTimer[ReqTypeVideo][true])
 }
 
 func TestRecordBidType(t *testing.T) {
diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go
index f76480f0..e4d6a4f7 100644
--- a/pbsmetrics/prometheus/prometheus_test.go
+++ b/pbsmetrics/prometheus/prometheus_test.go
@@ -881,6 +881,48 @@ func TestMetricAccumulationSpotCheck(t *testing.T) {
 		expectedValue)
 }
 
+func TestRecordRequestQueueTimeMetric(t *testing.T) {
+	performTest := func(m *Metrics, requestStatus bool, requestType pbsmetrics.RequestType, timeInSec float64) {
+		m.RecordRequestQueueTime(requestStatus, requestType, time.Duration(timeInSec*float64(time.Second)))
+	}
+
+	testCases := []struct {
+		description   string
+		status        string
+		testCase      func(m *Metrics)
+		expectedCount uint64
+		expectedSum   float64
+	}{
+		{
+			description: "Success",
+			status:      requestSuccessLabel,
+			testCase: func(m *Metrics) {
+				performTest(m, true, pbsmetrics.ReqTypeVideo, 2)
+			},
+			expectedCount: 1,
+			expectedSum:   2,
+		},
+		{
+			description: "TimeoutError",
+			status:      requestRejectLabel,
+			testCase: func(m *Metrics) {
+				performTest(m, false, pbsmetrics.ReqTypeVideo, 50)
+			},
+			expectedCount: 1,
+			expectedSum:   50,
+		},
+	}
+
+	m := createMetricsForTesting()
+	for _, test := range testCases {
+
+		test.testCase(m)
+
+		result := getHistogramFromHistogramVecByTwoKeys(m.requestsQueueTimer, requestTypeLabel, "video", requestStatusLabel, test.status)
+		assertHistogram(t, test.description, result, test.expectedCount, test.expectedSum)
+	}
+}
+
 func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) {
 	m := dto.Metric{}
 	counter.Write(&m)
@@ -906,6 +948,24 @@ func getHistogramFromHistogramVec(histogram *prometheus.HistogramVec, labelKey,
 	return result
 }
 
+func getHistogramFromHistogramVecByTwoKeys(histogram *prometheus.HistogramVec, label1Key, label1Value, label2Key, label2Value string) dto.Histogram {
+	var result dto.Histogram
+	processMetrics(histogram, func(m dto.Metric) {
+		for ind, label := range m.GetLabel() {
+			if label.GetName() == label1Key && label.GetValue() == label1Value {
+				valInd := ind
+				if ind == 1 {
+					valInd = 0
+				}
+				if m.Label[valInd].GetName() == label2Key && m.Label[valInd].GetValue() == label2Value {
+					result = *m.GetHistogram()
+				}
+			}
+		}
+	})
+	return result
+}
+
 func processMetrics(collector prometheus.Collector, handler func(m dto.Metric)) {
 	collectorChan := make(chan prometheus.Metric)
 	go func() {
diff --git a/router/aspects/request_timeout_handler_test.go b/router/aspects/request_timeout_handler_test.go
index 5283d5d5..cdc920c4 100644
--- a/router/aspects/request_timeout_handler_test.go
+++ b/router/aspects/request_timeout_handler_test.go
@@ -1,12 +1,14 @@
 package aspects
 
 import (
+	"github.com/julienschmidt/httprouter"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/pbsmetrics"
 	"net/http"
 	"net/http/httptest"
+	"strconv"
 	"testing"
-
-	"github.com/julienschmidt/httprouter"
-	"github.com/prebid/prebid-server/config"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -23,6 +25,7 @@ func TestAny(t *testing.T) {
 		expectedRespCodeMessage string
 		expectedRespBody        string
 		expectedRespBodyMessage string
+		requestStatusMetrics    bool
 	}{
 		{
 			//TestQueuedRequestTimeoutWithTimeout
@@ -33,6 +36,7 @@ func TestAny(t *testing.T) {
 			expectedRespCodeMessage: "Http response code is incorrect, should be 408",
 			expectedRespBody:        "Queued request processing time exceeded maximum",
 			expectedRespBodyMessage: "Body should have error message",
+			requestStatusMetrics:    false,
 		},
 		{
 			//TestQueuedRequestTimeoutNoTimeout
@@ -43,6 +47,7 @@ func TestAny(t *testing.T) {
 			expectedRespCodeMessage: "Http response code is incorrect, should be 200",
 			expectedRespBody:        "Executed",
 			expectedRespBodyMessage: "Body should be present in response",
+			requestStatusMetrics:    true,
 		},
 		{
 			//TestQueuedRequestNoHeaders
@@ -53,6 +58,7 @@ func TestAny(t *testing.T) {
 			expectedRespCodeMessage: "Http response code is incorrect, should be 200",
 			expectedRespBody:        "Executed",
 			expectedRespBodyMessage: "Body should be present in response",
+			requestStatusMetrics:    true,
 		},
 		{
 			//TestQueuedRequestSomeHeaders
@@ -63,31 +69,13 @@ func TestAny(t *testing.T) {
 			expectedRespCodeMessage: "Http response code is incorrect, should be 200",
 			expectedRespBody:        "Executed",
 			expectedRespBodyMessage: "Body should be present in response",
-		},
-		{
-			//TestQueuedRequestAllHeadersIncorrect
-			reqTimeInQueue:          "test1",
-			reqTimeOut:              "test2",
-			setHeaders:              true,
-			expectedRespCode:        http.StatusInternalServerError,
-			expectedRespCodeMessage: "Http response code is incorrect, should be 400",
-			expectedRespBody:        "Request timeout headers are incorrect (wrong format)",
-			expectedRespBodyMessage: "Body should have error message",
-		},
-		{
-			//TestQueuedRequestSomeHeadersIncorrect
-			reqTimeInQueue:          "test1",
-			reqTimeOut:              "123",
-			setHeaders:              true,
-			expectedRespCode:        http.StatusInternalServerError,
-			expectedRespCodeMessage: "Http response code is incorrect, should be 400",
-			expectedRespBody:        "Request timeout headers are incorrect (wrong format)",
-			expectedRespBodyMessage: "Body should have error message",
+			requestStatusMetrics:    true,
 		},
 	}
 
 	for _, test := range testCases {
-		result := ExecuteAspectRequest(t, test.reqTimeInQueue, test.reqTimeOut, test.setHeaders)
+		reqTimeFloat, _ := strconv.ParseFloat(test.reqTimeInQueue, 64)
+		result := ExecuteAspectRequest(t, test.reqTimeInQueue, test.reqTimeOut, test.setHeaders, pbsmetrics.ReqTypeVideo, test.requestStatusMetrics, reqTimeFloat)
 		assert.Equal(t, test.expectedRespCode, result.Code, test.expectedRespCodeMessage)
 		assert.Equal(t, test.expectedRespBody, string(result.Body.Bytes()), test.expectedRespBodyMessage)
 	}
@@ -101,7 +89,7 @@ func MockHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
 	w.Write([]byte("Executed"))
 }
 
-func ExecuteAspectRequest(t *testing.T, timeInQueue string, reqTimeout string, setHeaders bool) *httptest.ResponseRecorder {
+func ExecuteAspectRequest(t *testing.T, timeInQueue string, reqTimeout string, setHeaders bool, requestType pbsmetrics.RequestType, status bool, requestDuration float64) *httptest.ResponseRecorder {
 	rw := httptest.NewRecorder()
 	req, err := http.NewRequest("POST", "/test", nil)
 	if err != nil {
@@ -114,7 +102,11 @@ func ExecuteAspectRequest(t *testing.T, timeInQueue string, reqTimeout string, s
 
 	customHeaders := config.RequestTimeoutHeaders{reqTimeInQueueHeaderName, reqTimeoutHeaderName}
 
-	handler := QueuedRequestTimeout(MockEndpoint(), customHeaders)
+	metrics := &pbsmetrics.MetricsEngineMock{}
+
+	metrics.On("RecordRequestQueueTime", status, requestType, time.Duration(requestDuration*float64(time.Second))).Once()
+
+	handler := QueuedRequestTimeout(MockEndpoint(), customHeaders, metrics, requestType)
 
 	r := httprouter.New()
 	r.POST("/test", handler)