Repository: prebid/prebid-server
Pull request #2658 (2023-03-20 15:32:02)

Add overhead metrics
PR makes changes to add following overhead metrics for each adapters:

- `pre-bidder-request` metric:   include the amount of time adapters need for their MakeRequests calls (before making HTTP request to bidder server).
- `post-bidder-request` metric: include the amount of time adapters needed for preparing seat bids to openrtb response.

```
~ » curl -X GET http://localhost:9090/metrics | grep "request_overhead_time" | grep "appnexus" | grep "openrtb2-app"                                                                                                                    
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 3059k    0 3059k    0     0  24.7M      0 --:--:-- --:--:-- --:--:-- 26.6M
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="0.05"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="0.1"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="0.15"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="0.2"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="0.25"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="0.3"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="0.4"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="0.5"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="0.75"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="1"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app",le="+Inf"} 1
adapter_request_overhead_time_seconds_sum{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app"} -0.000435816
adapter_request_overhead_time_seconds_count{adapter="appnexus",overhead_type="post-bidder-request",request_type="openrtb2-app"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="0.05"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="0.1"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="0.15"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="0.2"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="0.25"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="0.3"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="0.4"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="0.5"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="0.75"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="1"} 1
adapter_request_overhead_time_seconds_bucket{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app",le="+Inf"} 1
adapter_request_overhead_time_seconds_sum{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app"} 0.002133976
adapter_request_overhead_time_seconds_count{adapter="appnexus",overhead_type="pre-bidder-request",request_type="openrtb2-app"} 1
```



VeronikaSolovei9: Gus, I added my comments and then page refreshed and I saw your comments :)

I think enable/disable a metric flag should be inside of `Disabled   DisabledMetrics   `mapstructure:"disabled_metrics"``
instead of a new field in Metrics struct. 
```
507   type Metrics struct {
508       Influxdb   InfluxMetrics     `mapstructure:"influxdb"`
509       Prometheus PrometheusMetrics `mapstructure:"prometheus"`
510       Disabled   DisabledMetrics   `mapstructure:"disabled_metrics"` <--- inside of DisabledMetrics
    +     EnableAdapterOverhead   *bool   `mapstructure:"enable_adapter_overhead_metrics"`
511   }
config/config.go
```

Agree? 
VeronikaSolovei9: I don't really like word "overhead". It's hard to understand what is it exactly. Can we think about different name for it? 

onkarvhanumante: @guscarreon  @VeronikaSolovei9 thanks for the suggestions.  Had some discussion within team and we have decided to change way overhead metrics are recorded,

- Overhead metrics are maintained at PBS level.  As of now no need to include adapter and request type as labels (Resolved earlier comments that were related to these labels).
- Now we are recording 3 overhead metrics:
  - `NonErrorPreBidderRequest` - used to measure the amount of time needed to make HTTP requests to bidder server
  - `NonErrorPrepareOrtbResponse` - used to measure the amount of time needed to prepare a single ORTB (OpenRTB) response from multiple bidder responses
  - `NonErrorPrepareOrtbBidderRequests` -  used to measure the amount of time needed to split a single OpenRTB request into multiple bidder ORTB requests

```
~ » curl -X GET http://localhost:9090/metrics | grep "overhead_time"          
...                                                                                                                                                         
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.05"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.15"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.2"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.25"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.3"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.4"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.5"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.75"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="+Inf"} 0
overhead_time_seconds_sum{overhead_type="non-error-pre-bidder-request"} 0
overhead_time_seconds_count{overhead_type="non-error-pre-bidder-request"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.05"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.15"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.2"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.25"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.3"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.4"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.5"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.75"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="+Inf"} 0
overhead_time_seconds_sum{overhead_type="non-error-prepare-ortb-bidder-requests"} 0
overhead_time_seconds_count{overhead_type="non-error-prepare-ortb-bidder-requests"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.05"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.15"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.2"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.25"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.3"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.4"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.5"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.75"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="+Inf"} 0
overhead_time_seconds_sum{overhead_type="non-error-prepare-ortb-response"} 0
overhead_time_seconds_count{overhead_type="non-error-prepare-ortb-response"} 0
```
	
onkarvhanumante: @guscarreon  @VeronikaSolovei9 thanks for the suggestions.  Had some discussion within team and we have decided to change way overhead metrics are recorded,

- Overhead metrics are defined at PBS level.  As of now no need to include adapter and request type as labels. Since defined at PBS level  no need to make them optional (Resolved earlier comments that were related to these labels).
- Now we are recording 3 overhead metrics:
  - `NonErrorPreBidderRequest` - used to measure the amount of time needed to make HTTP requests to bidder server
  - `NonErrorPrepareOrtbResponse` - used to measure the amount of time needed to prepare a single ORTB (OpenRTB) response from multiple bidder responses
  - `NonErrorPrepareOrtbBidderRequests` -  used to measure the amount of time needed to split a single OpenRTB request into multiple bidder ORTB requests

```
~ » curl -X GET http://localhost:9090/metrics | grep "overhead_time"          
...                                                                                                                                                         
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.05"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.15"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.2"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.25"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.3"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.4"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.5"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="0.75"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-pre-bidder-request",le="+Inf"} 0
overhead_time_seconds_sum{overhead_type="non-error-pre-bidder-request"} 0
overhead_time_seconds_count{overhead_type="non-error-pre-bidder-request"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.05"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.15"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.2"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.25"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.3"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.4"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.5"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="0.75"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-bidder-requests",le="+Inf"} 0
overhead_time_seconds_sum{overhead_type="non-error-prepare-ortb-bidder-requests"} 0
overhead_time_seconds_count{overhead_type="non-error-prepare-ortb-bidder-requests"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.05"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.15"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.2"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.25"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.3"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.4"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.5"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="0.75"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="1"} 0
overhead_time_seconds_bucket{overhead_type="non-error-prepare-ortb-response",le="+Inf"} 0
overhead_time_seconds_sum{overhead_type="non-error-prepare-ortb-response"} 0
overhead_time_seconds_count{overhead_type="non-error-prepare-ortb-response"} 0
```
	
onkarvhanumante: As mentioned https://github.com/prebid/prebid-server/pull/2658#pullrequestreview-1377803312
> We could store the `AfterMakeBidsStartTime` and `MakeBidsDurations` fields under the `AuctionRequest` itself. That's a concurrency-safe place and we would avoid adding more return values to `HoldAuction()`:
> 
> 
> Do you agree with this approach? Let me know your thoughts.

endpoint's handler doesn't pass `AuctionRequest` as an pointer reference passed to `HoldAuction()` . So values assigned in `HoldAuction` function won't be available in endpoint's handler. 

```
type Exchange interface {
	// HoldAuction executes an OpenRTB v2.5 Auction.
	HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error)
}
```
Here, we could update either update `HoldAuction` to accept `AuctionRequest` as pointer reference or other way is to update `HoldAuction` to return more values. 
```
// AuctionRequest holds the bid request for the auction
// and all other information needed to process that request
type AuctionRequest struct {
...
}
```
Also as per comments, `AuctionRequest` is mainly used to hold details needed to process auction request. On other hand  `AfterMakeBidsStartTime` and `MakeBidsDurations` fields are not related or needed for processing request. So I am slightly leaning towards updating `HoldAuction` to return more values.

```
type MakeBidsDurationInfo struct {
	AfterMakeBidsStartTime time.Time
	Durations              []time.Duration
}

...

type Exchange interface {
	// HoldAuction executes an OpenRTB v2.5 Auction.
	HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, map[openrtb_ext.BidderName]*adapters.MakeBidsDurationInfo, error)
}
...

endpoint handler:
response, makeBidsDurationMap, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)

...

func recordResponsePreparationMetrics(mbi map[openrtb_ext.BidderName]*adapters.MakeBidsDurationInfo) {
    +     for _, durationInfo := range mbi {
    +       .
    +       .
    +     }
    + }
endpoints/openrtb2/auction.go

```

@guscarreon let me know your thoughts on above.



guscarreon: > endpoint's handler doesn't pass `AuctionRequest` as an pointer reference passed to `HoldAuction()` . So values assigned in `HoldAuction` function won't be available in endpoint's handler

Great point @onkarvhanumante . We discussed this with the team this afternoon and went through the options you pose in your comment above. We believe it is safe and more desirable to pass `AuctionRequest` as a pointer. We don't seem to write into its fields anywhere inside `getAllBids()`, which is where parallelism happens. When implemented, please make sure to add a `nil` check at the beginning of `HoldAuction()`.

Please let me know if you agree with this approach


Patch:
diff --git a/adapters/bidder.go b/adapters/bidder.go
index bb7424f3..28fb555d 100644
--- a/adapters/bidder.go
+++ b/adapters/bidder.go
@@ -4,6 +4,7 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"net/http"
+	"time"
 
 	"github.com/prebid/openrtb/v19/openrtb2"
 	"github.com/prebid/prebid-server/config"
@@ -145,8 +146,15 @@ func (r *RequestData) SetBasicAuth(username string, password string) {
 
 type ExtraRequestInfo struct {
 	PbsEntryPoint              metrics.RequestType
+	BidderRequestStartTime     time.Time
 	GlobalPrivacyControlHeader string
 	CurrencyConversions        currency.Conversions
+	MakeBidsTimeInfo           MakeBidsTimeInfo
+}
+
+type MakeBidsTimeInfo struct {
+	AfterMakeBidsStartTime time.Time
+	Durations              []time.Duration
 }
 
 func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo {
diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go
index 20a6a58e..99072255 100644
--- a/adapters/rubicon/rubicon.go
+++ b/adapters/rubicon/rubicon.go
@@ -232,20 +232,39 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co
 	return bidder, nil
 }
 
-func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
-	requestCopy := request
-	if request.User != nil {
-		userCopy := *request.User
-		requestCopy.User = &userCopy
+func updateRequestTo26(r *openrtb2.BidRequest) error {
+	if r.Regs != nil {
+		regsCopy := *r.Regs
+		r.Regs = &regsCopy
+	}
+
+	if r.Source != nil {
+		sourceCopy := *r.Source
+		r.Source = &sourceCopy
+	}
+
+	if r.User != nil {
+		userCopy := *r.User
+		r.User = &userCopy
 	}
 
-	err := openrtb_ext.ConvertUpTo26(&openrtb_ext.RequestWrapper{BidRequest: requestCopy})
+	requestWrapper := &openrtb_ext.RequestWrapper{BidRequest: r}
+
+	if err := openrtb_ext.ConvertUpTo26(requestWrapper); err != nil {
+		return err
+	}
+
+	return requestWrapper.RebuildRequest()
+}
+
+func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+
+	err := updateRequestTo26(request)
+
 	if err != nil {
 		return nil, []error{err}
 	}
 
-	request = requestCopy
-
 	numRequests := len(request.Imp)
 	requestData := make([]*adapters.RequestData, 0, numRequests)
 	headers := http.Header{}
diff --git a/adapters/zeta_global_ssp/zeta_global_ssp.go b/adapters/zeta_global_ssp/zeta_global_ssp.go
new file mode 100644
index 00000000..8e80d77d
--- /dev/null
+++ b/adapters/zeta_global_ssp/zeta_global_ssp.go
@@ -0,0 +1,93 @@
+package zeta_global_ssp
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/prebid/openrtb/v19/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/prebid/prebid-server/util/httputil"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	requestJson, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	requestData := &adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Body:    requestJson,
+		Headers: headers,
+	}
+
+	return []*adapters.RequestData{requestData}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if httputil.IsResponseStatusCodeNoContent(responseData) {
+		return nil, nil
+	}
+
+	if err := httputil.CheckResponseStatusCodeForErrors(responseData); err != nil {
+		return nil, []error{err}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	bidResponse.Currency = response.Cur
+	var errors []error
+	for _, seatBid := range response.SeatBid {
+		for i := range seatBid.Bid {
+			bidType, err := getMediaTypeForBid(seatBid.Bid[i])
+			if err != nil {
+				errors = append(errors, err)
+				continue
+			}
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: bidType,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+	return bidResponse, errors
+}
+
+func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+	if bid.Ext != nil {
+		var bidExt openrtb_ext.ExtBid
+		err := json.Unmarshal(bid.Ext, &bidExt)
+		if err == nil && bidExt.Prebid != nil {
+			return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type))
+		}
+	}
+
+	return "", &errortypes.BadServerResponse{
+		Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID),
+	}
+}
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 8634710e..fb9af8ca 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -221,7 +221,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 
 	secGPC := r.Header.Get("Sec-GPC")
 
-	auctionRequest := exchange.AuctionRequest{
+	auctionRequest := &exchange.AuctionRequest{
 		BidRequestWrapper:          reqWrapper,
 		Account:                    *account,
 		UserSyncs:                  usersyncs,
@@ -266,6 +266,9 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	}
 
 	labels, ao = sendAmpResponse(w, hookExecutor, response, reqWrapper, account, labels, ao, errL)
+	if len(ao.Errors) == 0 {
+		recordResponsePreparationMetrics(auctionRequest.MakeBidsTimeInfo, deps.metricsEngine)
+	}
 }
 
 func rejectAmpRequest(
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 7dfa4507..efc74613 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -24,6 +24,7 @@ import (
 	nativeRequests "github.com/prebid/openrtb/v19/native1/request"
 	"github.com/prebid/openrtb/v19/openrtb2"
 	"github.com/prebid/openrtb/v19/openrtb3"
+	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/ortb"
 	"golang.org/x/net/publicsuffix"
@@ -179,6 +180,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 	}
 
 	if rejectErr := hookexecution.FindFirstRejectOrNil(errL); rejectErr != nil {
+		ao.Request = req.BidRequest
 		labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao)
 		return
 	}
@@ -212,7 +214,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 
 	warnings := errortypes.WarningOnly(errL)
 
-	auctionRequest := exchange.AuctionRequest{
+	auctionRequest := &exchange.AuctionRequest{
 		BidRequestWrapper:          req,
 		Account:                    *account,
 		UserSyncs:                  usersyncs,
@@ -251,6 +253,9 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 	}
 
 	labels, ao = sendAuctionResponse(w, hookExecutor, response, req.BidRequest, account, labels, ao)
+	if len(ao.Errors) == 0 {
+		recordResponsePreparationMetrics(auctionRequest.MakeBidsTimeInfo, deps.metricsEngine)
+	}
 }
 
 func rejectAuctionRequest(
@@ -2325,3 +2330,13 @@ func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage
 func generateStoredBidResponseValidationError(impID string) error {
 	return fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impID)
 }
+
+func recordResponsePreparationMetrics(mbti map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo, me metrics.MetricsEngine) {
+	for _, info := range mbti {
+		duration := time.Since(info.AfterMakeBidsStartTime)
+		for _, makeBidsDuration := range info.Durations {
+			duration += makeBidsDuration
+		}
+		me.RecordOverheadTime(metrics.MakeAuctionResponse, duration)
+	}
+}
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 829b1f88..79ac2614 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -297,7 +297,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	}
 
 	secGPC := r.Header.Get("Sec-GPC")
-	auctionRequest := exchange.AuctionRequest{
+	auctionRequest := &exchange.AuctionRequest{
 		BidRequestWrapper:          bidReqWrapper,
 		Account:                    *account,
 		UserSyncs:                  usersyncs,
@@ -354,6 +354,10 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		return
 	}
 
+	if len(vo.Errors) == 0 {
+		recordResponsePreparationMetrics(auctionRequest.MakeBidsTimeInfo, deps.metricsEngine)
+	}
+
 	w.Header().Set("Content-Type", "application/json")
 	w.Write(resp)
 
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 77896761..34bb320e 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -166,6 +166,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/yieldmo"
 	"github.com/prebid/prebid-server/adapters/yieldone"
 	"github.com/prebid/prebid-server/adapters/zeroclickfraud"
+	"github.com/prebid/prebid-server/adapters/zeta_global_ssp"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
@@ -353,5 +354,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderYieldmo:           yieldmo.Builder,
 		openrtb_ext.BidderYieldone:          yieldone.Builder,
 		openrtb_ext.BidderZeroClickFraud:    zeroclickfraud.Builder,
+		openrtb_ext.BidderZetaGlobalSsp:     zeta_global_ssp.Builder,
 	}
 }
diff --git a/exchange/bidder.go b/exchange/bidder.go
index f2cf790f..58dc1789 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -121,9 +121,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 		return nil, []error{reject}
 	}
 
-	var reqData []*adapters.RequestData
-	var errs []error
-	var responseChannel chan *httpCallInfo
+	var (
+		reqData         []*adapters.RequestData
+		errs            []error
+		responseChannel chan *httpCallInfo
+	)
 
 	//check if real request exists for this bidder or it only has stored responses
 	dataLen := 0
@@ -169,11 +171,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 		dataLen = len(reqData) + len(bidderRequest.BidderStoredResponses)
 		responseChannel = make(chan *httpCallInfo, dataLen)
 		if len(reqData) == 1 {
-			responseChannel <- bidder.doRequest(ctx, reqData[0])
+			responseChannel <- bidder.doRequest(ctx, reqData[0], reqInfo.BidderRequestStartTime)
 		} else {
 			for _, oneReqData := range reqData {
 				go func(data *adapters.RequestData) {
-					responseChannel <- bidder.doRequest(ctx, data)
+					responseChannel <- bidder.doRequest(ctx, data, reqInfo.BidderRequestStartTime)
 				}(oneReqData) // Method arg avoids a race condition on oneReqData
 			}
 		}
@@ -227,6 +229,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 		}
 
 		if httpInfo.err == nil {
+			startTime := time.Now()
 			bidResponse, moreErrs := bidder.Bidder.MakeBids(bidderRequest.BidRequest, httpInfo.request, httpInfo.response)
 			errs = append(errs, moreErrs...)
 
@@ -363,11 +366,12 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 					errs = append(errs, err)
 				}
 			}
+			reqInfo.MakeBidsTimeInfo.Durations = append(reqInfo.MakeBidsTimeInfo.Durations, time.Since(startTime))
 		} else {
 			errs = append(errs, httpInfo.err)
 		}
 	}
-
+	reqInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime = time.Now()
 	seatBids := make([]*entities.PbsOrtbSeatBid, 0, len(seatBidMap))
 	for _, seatBid := range seatBidMap {
 		seatBids = append(seatBids, seatBid)
@@ -487,11 +491,11 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall {
 
 // doRequest makes a request, handles the response, and returns the data needed by the
 // Bidder interface.
-func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData) *httpCallInfo {
-	return bidder.doRequestImpl(ctx, req, glog.Warningf)
+func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData, pbsRequestStartTime time.Time) *httpCallInfo {
+	return bidder.doRequestImpl(ctx, req, glog.Warningf, pbsRequestStartTime)
 }
 
-func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg) *httpCallInfo {
+func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, pbsRequestStartTime time.Time) *httpCallInfo {
 	var requestBody []byte
 
 	switch strings.ToUpper(bidder.config.EndpointCompression) {
@@ -515,6 +519,7 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re
 	if !bidder.config.DisableConnMetrics {
 		ctx = bidder.addClientTrace(ctx)
 	}
+	bidder.me.RecordOverheadTime(metrics.PreBidder, time.Since(pbsRequestStartTime))
 	httpResp, err := ctxhttp.Do(ctx, bidder.Client, httpReq)
 	if err != nil {
 		if err == context.DeadlineExceeded {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index fa0ea6af..c3728180 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -47,7 +47,7 @@ type extCacheInstructions struct {
 // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines.
 type Exchange interface {
 	// HoldAuction executes an OpenRTB v2.5 Auction.
-	HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error)
+	HoldAuction(ctx context.Context, r *AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error)
 }
 
 // IdFetcher can find the user's ID for a specific Bidder.
@@ -86,7 +86,8 @@ type seatResponseExtra struct {
 	Warnings           []openrtb_ext.ExtBidderMessage
 	// httpCalls is the list of debugging info. It should only be populated if the request.test == 1.
 	// This will become response.ext.debug.httpcalls.{bidder} on the final Response.
-	HttpCalls []*openrtb_ext.ExtHttpCall
+	HttpCalls        []*openrtb_ext.ExtHttpCall
+	MakeBidsTimeInfo adapters.MakeBidsTimeInfo
 }
 
 type bidResponseWrapper struct {
@@ -205,6 +206,8 @@ type AuctionRequest struct {
 	PubID                 string
 	HookExecutor          hookexecution.StageExecutor
 	QueryParams           url.Values
+	// map of bidder to store duration needed for the MakeBids() calls and start time after MakeBids() calls
+	MakeBidsTimeInfo map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo
 }
 
 // BidderRequest holds the bidder specific request and all other
@@ -218,7 +221,11 @@ type BidderRequest struct {
 	ImpReplaceImpId       map[string]bool
 }
 
-func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) {
+func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) {
+	if r == nil {
+		return nil, nil
+	}
+
 	var floorErrs []error
 	err := r.HookExecutor.ExecuteProcessedAuctionStage(r.BidRequestWrapper)
 	if err != nil {
@@ -307,7 +314,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 		Prebid: *requestExtPrebid,
 		SChain: requestExt.GetSChain(),
 	}
-	bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, r, requestExtLegacy, gdprDefaultValue)
+	bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprDefaultValue)
 	errs = append(errs, floorErrs...)
 
 	e.me.RecordRequestPrivacy(privacyLabels)
@@ -321,13 +328,14 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	auctionCtx, cancel := e.makeAuctionContext(ctx, cacheInstructions.cacheBids)
 	defer cancel()
 
-	var adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
-	var adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra
-	var fledge *openrtb_ext.Fledge
-	var anyBidsReturned bool
-
-	// List of bidders we have requests for.
-	var liveAdapters []openrtb_ext.BidderName
+	var (
+		adapterBids     map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
+		adapterExtra    map[openrtb_ext.BidderName]*seatResponseExtra
+		fledge          *openrtb_ext.Fledge
+		anyBidsReturned bool
+		// List of bidders we have requests for.
+		liveAdapters []openrtb_ext.BidderName
+	)
 
 	if len(r.StoredAuctionResponses) > 0 {
 		adapterBids, fledge, liveAdapters, err = buildStoredAuctionResponse(r.StoredAuctionResponses)
@@ -348,12 +356,16 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 			alternateBidderCodes = *r.Account.AlternateBidderCodes
 		}
 
-		adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor)
+		adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime)
+		r.MakeBidsTimeInfo = buildMakeBidsTimeInfoMap(adapterExtra)
 	}
 
-	var auc *auction
-	var cacheErrs []error
-	var bidResponseExt *openrtb_ext.ExtBidResponse
+	var (
+		auc            *auction
+		cacheErrs      []error
+		bidResponseExt *openrtb_ext.ExtBidResponse
+	)
+
 	if anyBidsReturned {
 		var bidCategory map[string]string
 		//If includebrandcategory is present in ext then CE feature is on.
@@ -397,7 +409,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 				errs = append(errs, dealErrs...)
 			}
 
-			bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
+			bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
 			if debugLog.DebugEnabledOrOverridden {
 				if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
 					debugLog.Data.Response = string(bidRespExtBytes)
@@ -414,9 +426,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 
 			targData.setTargeting(auc, r.BidRequestWrapper.BidRequest.App != nil, bidCategory, r.Account.TruncateTargetAttribute, multiBidMap)
 		}
-		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
+		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
 	} else {
-		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
+		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
 
 		if debugLog.DebugEnabledOrOverridden {
 
@@ -453,10 +465,17 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	bidResponse = adservertargeting.Apply(r.BidRequestWrapper, r.ResolvedBidRequest, bidResponse, r.QueryParams, bidResponseExt, r.Account.TruncateTargetAttribute)
 
 	bidResponse.Ext, err = encodeBidResponseExt(bidResponseExt)
-
 	return bidResponse, err
 }
 
+func buildMakeBidsTimeInfoMap(adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra) map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo {
+	makeBidsInfoMap := make(map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo)
+	for bidderName, responseExtra := range adapterExtra {
+		makeBidsInfoMap[bidderName] = responseExtra.MakeBidsTimeInfo
+	}
+	return makeBidsInfoMap
+}
+
 func buildMultiBidMap(prebid *openrtb_ext.ExtRequestPrebid) map[string]openrtb_ext.ExtMultiBid {
 	if prebid == nil || prebid.MultiBid == nil {
 		return nil
@@ -609,7 +628,8 @@ func (e *exchange) getAllBids(
 	headerDebugAllowed bool,
 	alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes,
 	experiment *openrtb_ext.Experiment,
-	hookExecutor hookexecution.StageExecutor) (
+	hookExecutor hookexecution.StageExecutor,
+	pbsRequestStartTime time.Time) (
 	map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
 	map[openrtb_ext.BidderName]*seatResponseExtra,
 	*openrtb_ext.Fledge,
@@ -620,6 +640,8 @@ func (e *exchange) getAllBids(
 	chBids := make(chan *bidResponseWrapper, len(bidderRequests))
 	bidsFound := false
 
+	e.me.RecordOverheadTime(metrics.MakeBidderRequests, time.Since(pbsRequestStartTime))
+
 	for _, bidder := range bidderRequests {
 		// Here we actually call the adapters and collect the bids.
 		bidderRunner := e.recoverSafely(bidderRequests, func(bidderRequest BidderRequest, conversions currency.Conversions) {
@@ -640,6 +662,7 @@ func (e *exchange) getAllBids(
 			reqInfo := adapters.NewExtraRequestInfo(conversions)
 			reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType
 			reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader
+			reqInfo.BidderRequestStartTime = start
 
 			bidReqOptions := bidRequestOptions{
 				accountDebugAllowed: accountDebugAllowed,
@@ -658,9 +681,13 @@ func (e *exchange) getAllBids(
 			if len(seatBids) != 0 {
 				ae.HttpCalls = seatBids[0].HttpCalls
 			}
-
+			// SeatBidsPreparationStartTime is needed to calculate duration for openrtb response preparation time metric
+			//  No metric needs to be logged for requests which error out
+			if err == nil {
+				ae.MakeBidsTimeInfo = reqInfo.MakeBidsTimeInfo
+			}
 			// Timing statistics
-			e.me.RecordAdapterTime(bidderRequest.BidderLabels, time.Since(start))
+			e.me.RecordAdapterTime(bidderRequest.BidderLabels, elapsed)
 			bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterSeatBids)
 			bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err)
 			// Append any bid validation errors to the error list
diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go
index cc9f4da0..95e2614d 100644
--- a/metrics/config/metrics.go
+++ b/metrics/config/metrics.go
@@ -170,6 +170,13 @@ func (me *MultiMetricsEngine) RecordAdapterTime(labels metrics.AdapterLabels, le
 	}
 }
 
+// RecordOverheadTime across all engines
+func (me *MultiMetricsEngine) RecordOverheadTime(overhead metrics.OverheadType, length time.Duration) {
+	for _, thisME := range *me {
+		thisME.RecordOverheadTime(overhead, length)
+	}
+}
+
 // RecordCookieSync across all engines
 func (me *MultiMetricsEngine) RecordCookieSync(status metrics.CookieSyncStatus) {
 	for _, thisME := range *me {
@@ -430,6 +437,10 @@ func (me *NilMetricsEngine) RecordAdapterPrice(labels metrics.AdapterLabels, cpm
 func (me *NilMetricsEngine) RecordAdapterTime(labels metrics.AdapterLabels, length time.Duration) {
 }
 
+// RecordOverheadTime as a noop
+func (me *NilMetricsEngine) RecordOverheadTime(overhead metrics.OverheadType, length time.Duration) {
+}
+
 // RecordCookieSync as a noop
 func (me *NilMetricsEngine) RecordCookieSync(status metrics.CookieSyncStatus) {
 }
diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go
index 8563fcb1..44f2ecd7 100644
--- a/metrics/go_metrics.go
+++ b/metrics/go_metrics.go
@@ -11,7 +11,6 @@ import (
 	metrics "github.com/rcrowley/go-metrics"
 )
 
-// Metrics is the legacy Metrics object (go-metrics) expanded to also satisfy the MetricsEngine interface
 type Metrics struct {
 	MetricsRegistry                metrics.Registry
 	ConnectionCounter              metrics.Counter
@@ -34,8 +33,7 @@ type Metrics struct {
 	TLSHandshakeTimer              metrics.Timer
 	StoredResponsesMeter           metrics.Meter
 
-	// Metrics for OpenRTB requests specifically. So we can track what % of RequestsMeter are OpenRTB
-	// and know when legacy requests have been abandoned.
+	// Metrics for OpenRTB requests specifically
 	RequestStatuses       map[RequestType]map[RequestStatus]metrics.Meter
 	AmpNoCookieMeter      metrics.Meter
 	CookieSyncMeter       metrics.Meter
@@ -79,6 +77,8 @@ type Metrics struct {
 
 	// Module metrics
 	ModuleMetrics map[string]map[string]*ModuleMetrics
+
+	OverheadTimer map[OverheadType]metrics.Timer
 }
 
 // AdapterMetrics houses the metrics for a particular adapter
@@ -155,7 +155,7 @@ type ModuleMetrics struct {
 // testing routines to ensure that no metrics are written anywhere.
 //
 // This will be useful when removing endpoints, we can just run will the blank metrics function
-// rather than loading legacy metrics that never get filled.
+// rather than loading metrics that never get filled.
 // This will also eventually let us configure metrics, such as setting a limited set of metrics
 // for a production instance, and then expanding again when we need more debugging.
 func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disabledMetrics config.DisabledMetrics, moduleStageNames map[string][]string) *Metrics {
@@ -218,6 +218,8 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 
 		exchanges: exchanges,
 		modules:   getModuleNames(moduleStageNames),
+
+		OverheadTimer: makeBlankOverheadTimerMetrics(),
 	}
 
 	for _, a := range exchanges {
@@ -302,6 +304,7 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
 	newMetrics.PrebidCacheRequestTimerSuccess = metrics.GetOrRegisterTimer("prebid_cache_request_time.ok", registry)
 	newMetrics.PrebidCacheRequestTimerError = metrics.GetOrRegisterTimer("prebid_cache_request_time.err", registry)
 	newMetrics.StoredResponsesMeter = metrics.GetOrRegisterMeter("stored_responses", registry)
+	newMetrics.OverheadTimer = makeOverheadTimerMetrics(registry)
 
 	for _, dt := range StoredDataTypes() {
 		for _, ft := range StoredDataFetchTypes() {
@@ -379,6 +382,15 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
 	return newMetrics
 }
 
+func makeBlankOverheadTimerMetrics() map[OverheadType]metrics.Timer {
+	m := make(map[OverheadType]metrics.Timer)
+	overheads := OverheadTypes()
+	for idx := range overheads {
+		m[overheads[idx]] = &metrics.NilTimer{}
+	}
+	return m
+}
+
 // Part of setting up blank metrics, the adapter metrics.
 func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMetrics {
 	blankMeter := &metrics.NilMeter{}
@@ -445,6 +457,15 @@ func makeBlankMarkupDeliveryMetrics() *MarkupDeliveryMetrics {
 	}
 }
 
+func makeOverheadTimerMetrics(registry metrics.Registry) map[OverheadType]metrics.Timer {
+	m := make(map[OverheadType]metrics.Timer)
+	overheads := OverheadTypes()
+	for idx := range overheads {
+		m[overheads[idx]] = metrics.GetOrRegisterTimer("request_over_head_time."+overheads[idx].String(), registry)
+	}
+	return m
+}
+
 func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string, exchange string, am *AdapterMetrics) {
 	am.NoCookieMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.no_cookie_requests", adapterOrAccount, exchange), registry)
 	am.NoBidMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.requests.nobid", adapterOrAccount, exchange), registry)
@@ -847,6 +868,11 @@ func (me *Metrics) RecordAdapterTime(labels AdapterLabels, length time.Duration)
 	}
 }
 
+// RecordOverheadTime implements a part of the MetricsEngine interface. Records the adapter overhead time
+func (me *Metrics) RecordOverheadTime(overhead OverheadType, length time.Duration) {
+	me.OverheadTimer[overhead].Update(length)
+}
+
 // RecordCookieSync implements a part of the MetricsEngine interface. Records a cookie sync request
 func (me *Metrics) RecordCookieSync(status CookieSyncStatus) {
 	me.CookieSyncMeter.Mark(1)
diff --git a/metrics/metrics.go b/metrics/metrics.go
index c1a65b44..186e1ffa 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -26,6 +26,26 @@ type AdapterLabels struct {
 	AdapterErrors map[AdapterError]struct{}
 }
 
+// OverheadType: overhead type enumeration
+type OverheadType string
+
+const (
+	// PreBidder - measures the time needed to execute the adapter's MakeRequests() implementation, build Prebid headers and apply GZip compression if needed
+	PreBidder OverheadType = "pre-bidder"
+	// MakeAuctionResponse - measures the amount of time spent doing all the MakeBids() calls as well as preparing PBS's response
+	MakeAuctionResponse OverheadType = "make-auction-response"
+	// MakeBidderRequests - measures the time needed to fetch a stored request (if needed), parse, unmarshal, and validate the OpenRTB request, interpret its privacy policies, and split it into multiple requests sanitized for each bidder
+	MakeBidderRequests OverheadType = "make-bidder-requests"
+)
+
+func (t OverheadType) String() string {
+	return string(t)
+}
+
+func OverheadTypes() []OverheadType {
+	return []OverheadType{PreBidder, MakeAuctionResponse, MakeBidderRequests}
+}
+
 // ImpLabels defines metric labels describing the impression type.
 type ImpLabels struct {
 	BannerImps bool
@@ -157,24 +177,14 @@ func DemandTypes() []DemandSource {
 
 // The request types (endpoints)
 const (
-	ReqTypeLegacy   RequestType = "legacy"
 	ReqTypeORTB2Web RequestType = "openrtb2-web"
 	ReqTypeORTB2App RequestType = "openrtb2-app"
 	ReqTypeAMP      RequestType = "amp"
 	ReqTypeVideo    RequestType = "video"
 )
 
-// The media types described in the "imp" json objects
-const (
-	ImpTypeBanner ImpMediaType = "banner"
-	ImpTypeVideo  ImpMediaType = "video"
-	ImpTypeAudio  ImpMediaType = "audio"
-	ImpTypeNative ImpMediaType = "native"
-)
-
 func RequestTypes() []RequestType {
 	return []RequestType{
-		ReqTypeLegacy,
 		ReqTypeORTB2Web,
 		ReqTypeORTB2App,
 		ReqTypeAMP,
@@ -182,6 +192,14 @@ func RequestTypes() []RequestType {
 	}
 }
 
+// The media types described in the "imp" json objects
+const (
+	ImpTypeBanner ImpMediaType = "banner"
+	ImpTypeVideo  ImpMediaType = "video"
+	ImpTypeAudio  ImpMediaType = "audio"
+	ImpTypeNative ImpMediaType = "native"
+)
+
 func ImpTypes() []ImpMediaType {
 	return []ImpMediaType{
 		ImpTypeBanner,
@@ -407,13 +425,12 @@ type MetricsEngine interface {
 	RecordRequest(labels Labels)                           // ignores adapter. only statusOk and statusErr fom status
 	RecordImps(labels ImpLabels)                           // RecordImps across openRTB2 engines that support the 'Native' Imp Type
 	RecordRequestTime(labels Labels, length time.Duration) // ignores adapter. only statusOk and statusErr fom status
+	RecordOverheadTime(overHead OverheadType, length time.Duration)
 	RecordAdapterRequest(labels AdapterLabels)
 	RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration)
 	RecordDNSTime(dnsLookupTime time.Duration)
 	RecordTLSHandshakeTime(tlsHandshakeTime time.Duration)
 	RecordAdapterPanic(labels AdapterLabels)
-	// This records whether or not a bid of a particular type uses `adm` or `nurl`.
-	// Since the legacy endpoints don't have a bid type, it can only count bids from OpenRTB and AMP.
 	RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool)
 	RecordAdapterPrice(labels AdapterLabels, cpm float64)
 	RecordAdapterTime(labels AdapterLabels, length time.Duration)
diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go
index cc73682e..cbca8570 100644
--- a/metrics/metrics_mock.go
+++ b/metrics/metrics_mock.go
@@ -86,6 +86,11 @@ func (me *MetricsEngineMock) RecordAdapterTime(labels AdapterLabels, length time
 	me.Called(labels, length)
 }
 
+// RecordOverheadTime mock
+func (me *MetricsEngineMock) RecordOverheadTime(overhead OverheadType, length time.Duration) {
+	me.Called(overhead, length)
+}
+
 // RecordCookieSync mock
 func (me *MetricsEngineMock) RecordCookieSync(status CookieSyncStatus) {
 	me.Called(status)
diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go
index 61ae568d..dae7c14d 100644
--- a/metrics/prometheus/preload.go
+++ b/metrics/prometheus/preload.go
@@ -16,6 +16,7 @@ func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[st
 		connectionErrorValues     = []string{connectionAcceptError, connectionCloseError}
 		cookieValues              = cookieTypesAsString()
 		cookieSyncStatusValues    = cookieSyncStatusesAsString()
+		overheadTypes             = overheadTypesAsString()
 		requestTypeValues         = requestTypesAsString()
 		requestStatusValues       = requestStatusesAsString()
 		storedDataFetchTypeValues = storedDataFetchTypesAsString()
@@ -187,6 +188,10 @@ func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[st
 		adapterLabel: adapterValues,
 	})
 
+	preloadLabelValuesForHistogram(m.overheadTimer, map[string][]string{
+		overheadTypeLabel: overheadTypes,
+	})
+
 	preloadLabelValuesForCounter(m.syncerRequests, map[string][]string{
 		syncerLabel: syncerKeys,
 		statusLabel: syncerRequestStatusValues,
diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go
index 0d3eaa82..ee11dccf 100644
--- a/metrics/prometheus/prometheus.go
+++ b/metrics/prometheus/prometheus.go
@@ -24,7 +24,6 @@ type Metrics struct {
 	cookieSync                   *prometheus.CounterVec
 	setUid                       *prometheus.CounterVec
 	impressions                  *prometheus.CounterVec
-	impressionsLegacy            prometheus.Counter
 	prebidCacheWriteTimer        *prometheus.HistogramVec
 	requests                     *prometheus.CounterVec
 	debugRequests                prometheus.Counter
@@ -63,6 +62,7 @@ type Metrics struct {
 	adapterPanics                         *prometheus.CounterVec
 	adapterPrices                         *prometheus.HistogramVec
 	adapterRequests                       *prometheus.CounterVec
+	overheadTimer                         *prometheus.HistogramVec
 	adapterRequestsTimer                  *prometheus.HistogramVec
 	adapterReusedConnections              *prometheus.CounterVec
 	adapterCreatedConnections             *prometheus.CounterVec
@@ -130,6 +130,7 @@ const (
 	isVideoLabel         = "video"
 	markupDeliveryLabel  = "delivery"
 	optOutLabel          = "opt_out"
+	overheadTypeLabel    = "overhead_type"
 	privacyBlockedLabel  = "privacy_blocked"
 	requestStatusLabel   = "request_status"
 	requestTypeLabel     = "request_type"
@@ -176,6 +177,7 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet
 	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}
+	overheadTimeBuckets := []float64{0.00005, 0.0001, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.05}
 
 	metrics := Metrics{}
 	reg := prometheus.NewRegistry()
@@ -209,10 +211,6 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet
 		"Count of requested impressions to Prebid Server labeled by type.",
 		[]string{isBannerLabel, isVideoLabel, isAudioLabel, isNativeLabel})
 
-	metrics.impressionsLegacy = newCounterWithoutLabels(cfg, reg,
-		"impressions_requests_legacy",
-		"Count of requested impressions to Prebid Server using the legacy endpoint.")
-
 	metrics.prebidCacheWriteTimer = newHistogramVec(cfg, reg,
 		"prebidcache_write_time_seconds",
 		"Seconds to write to Prebid Cache labeled by success or failure. Failure timing is limited by Prebid Server enforced timeouts.",
@@ -430,6 +428,12 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet
 		"Count that tracks number of bids removed from bid response that had a invalid bidAdm (warn)",
 		[]string{adapterLabel, successLabel})
 
+	metrics.overheadTimer = newHistogramVec(cfg, reg,
+		"overhead_time_seconds",
+		"Seconds to prepare adapter request or resolve adapter response",
+		[]string{overheadTypeLabel},
+		overheadTimeBuckets)
+
 	metrics.adapterRequestsTimer = newHistogramVec(cfg, reg,
 		"adapter_request_time_seconds",
 		"Seconds to resolve each successful request labeled by adapter.",
@@ -913,6 +917,12 @@ func (m *Metrics) RecordAdapterPrice(labels metrics.AdapterLabels, cpm float64)
 	}).Observe(cpm)
 }
 
+func (m *Metrics) RecordOverheadTime(overhead metrics.OverheadType, duration time.Duration) {
+	m.overheadTimer.With(prometheus.Labels{
+		overheadTypeLabel: overhead.String(),
+	}).Observe(duration.Seconds())
+}
+
 func (m *Metrics) RecordAdapterTime(labels metrics.AdapterLabels, length time.Duration) {
 	if len(labels.AdapterErrors) == 0 {
 		m.adapterRequestsTimer.With(prometheus.Labels{
diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go
index d99f2f6f..07e4e89c 100644
--- a/metrics/prometheus/type_conversion.go
+++ b/metrics/prometheus/type_conversion.go
@@ -77,6 +77,15 @@ func syncerRequestStatusesAsString() []string {
 	return valuesAsString
 }
 
+func overheadTypesAsString() []string {
+	overheadTypes := metrics.OverheadTypes()
+	overheadTypesAsString := make([]string, len(overheadTypes))
+	for i, ot := range overheadTypes {
+		overheadTypesAsString[i] = ot.String()
+	}
+	return overheadTypesAsString
+}
+
 func syncerSetStatusesAsString() []string {
 	values := metrics.SyncerSetUidStatuses()
 	valuesAsString := make([]string, len(values))
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 171d31bb..c2a7d62f 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -265,6 +265,7 @@ const (
 	BidderYieldmo           BidderName = "yieldmo"
 	BidderYieldone          BidderName = "yieldone"
 	BidderZeroClickFraud    BidderName = "zeroclickfraud"
+	BidderZetaGlobalSsp     BidderName = "zeta_global_ssp"
 )
 
 // CoreBidderNames returns a slice of all core bidders.
@@ -449,6 +450,7 @@ func CoreBidderNames() []BidderName {
 		BidderYieldmo,
 		BidderYieldone,
 		BidderZeroClickFraud,
+		BidderZetaGlobalSsp,
 	}
 }
 
diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml
index 1014e5a1..4eb5f17c 100644
--- a/static/bidder-info/smaato.yaml
+++ b/static/bidder-info/smaato.yaml
@@ -1,4 +1,5 @@
 endpoint: "https://prebid.ad.smaato.net/oapi/prebid"
+endpointCompression: gzip
 maintainer:
   email: "prebid@smaato.com"
 gvlVendorID: 82
diff --git a/static/bidder-info/zeta_global_ssp.yaml b/static/bidder-info/zeta_global_ssp.yaml
new file mode 100644
index 00000000..3cc2ecb5
--- /dev/null
+++ b/static/bidder-info/zeta_global_ssp.yaml
@@ -0,0 +1,20 @@
+endpoint: https://ssp.disqus.com/bid/prebid-server?sid=GET_SID_FROM_ZETA&shortname=GET_SHORTNAME_FROM_ZETA
+endpointCompression: gzip
+maintainer:
+  email: DL-Zeta-SSP@zetaglobal.com
+gvlVendorID: 833
+modifyingVastXmlAllowed: true
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+  site:
+    mediaTypes:
+      - banner
+      - video
+userSync:
+  redirect:
+    url: https://ssp.disqus.com/redirectuser?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}&partner=GET_SHORTNAME_FROM_ZETA
+    userMacro: 'BUYERUID'
+
diff --git a/static/bidder-params/zeta_global_ssp.json b/static/bidder-params/zeta_global_ssp.json
new file mode 100644
index 00000000..acfaa9c1
--- /dev/null
+++ b/static/bidder-params/zeta_global_ssp.json
@@ -0,0 +1,8 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Zeta Global SSP Adapter Params",
+  "description": "A schema which validates params accepted by the Zeta Global SSP adapter",
+
+  "type": "object",
+  "properties": {}
+}
diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go
index b24ead2d..19b010fb 100644
--- a/stored_responses/stored_responses.go
+++ b/stored_responses/stored_responses.go
@@ -146,8 +146,9 @@ func ProcessStoredResponses(ctx context.Context, requestJson []byte, storedRespF
 		}
 		bidderImpIdReplaceImp := flipMap(impBidderReplaceImp)
 
-		impIdToStoredResp, impBidderToStoredBidResponse := buildStoredResponsesMaps(storedResponses, impBidderToStoredBidResponseId, impIdToRespId)
-		return impIdToStoredResp, impBidderToStoredBidResponse, bidderImpIdReplaceImp, nil
+		impIdToStoredResp, impBidderToStoredBidResponse, errs := buildStoredResponsesMaps(storedResponses, impBidderToStoredBidResponseId, impIdToRespId)
+
+		return impIdToStoredResp, impBidderToStoredBidResponse, bidderImpIdReplaceImp, errs
 	}
 	return nil, nil, nil, nil
 }
@@ -166,24 +167,33 @@ func flipMap(impBidderReplaceImpId ImpBidderReplaceImpID) BidderImpReplaceImpID
 	return flippedMap
 }
 
-func buildStoredResponsesMaps(storedResponses StoredResponseIdToStoredResponse, impBidderToStoredBidResponseId ImpBiddersWithBidResponseIDs, impIdToRespId ImpsWithAuctionResponseIDs) (ImpsWithBidResponses, ImpBidderStoredResp) {
+func buildStoredResponsesMaps(storedResponses StoredResponseIdToStoredResponse, impBidderToStoredBidResponseId ImpBiddersWithBidResponseIDs, impIdToRespId ImpsWithAuctionResponseIDs) (ImpsWithBidResponses, ImpBidderStoredResp, []error) {
+	var errs []error
 	//imp id to stored resp body
 	impIdToStoredResp := ImpsWithBidResponses{}
 	//stored bid responses: imp id to bidder to stored response body
 	impBidderToStoredBidResponse := ImpBidderStoredResp{}
 
 	for impId, respId := range impIdToRespId {
-		impIdToStoredResp[impId] = storedResponses[respId]
+		if len(storedResponses[respId]) == 0 {
+			errs = append(errs, fmt.Errorf("failed to fetch stored auction response for impId = %s and storedAuctionResponse id = %s", impId, respId))
+		} else {
+			impIdToStoredResp[impId] = storedResponses[respId]
+		}
 	}
 
 	for impId, bidderStoredResp := range impBidderToStoredBidResponseId {
 		bidderStoredResponses := StoredResponseIdToStoredResponse{}
 		for bidderName, id := range bidderStoredResp {
-			bidderStoredResponses[bidderName] = storedResponses[id]
+			if len(storedResponses[id]) == 0 {
+				errs = append(errs, fmt.Errorf("failed to fetch stored bid response for impId = %s, bidder = %s and storedBidResponse id = %s", impId, bidderName, id))
+			} else {
+				bidderStoredResponses[bidderName] = storedResponses[id]
+			}
 		}
 		impBidderToStoredBidResponse[impId] = bidderStoredResponses
 	}
-	return impIdToStoredResp, impBidderToStoredBidResponse
+	return impIdToStoredResp, impBidderToStoredBidResponse, errs
 }
 
 // parseImpInfo parses the request JSON and returns the impressions with their unmarshalled imp.ext.prebid


Test patch:
diff --git a/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json b/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json
index 2f5dee2d..581a0eb5 100644
--- a/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json
+++ b/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json
@@ -60,7 +60,6 @@
         "lon": -122.346200
       },
       "gender": "f",
-      "consent": "consent",
       "data": [
         {
           "ext": {
@@ -114,23 +113,31 @@
             }
           ]
         }
-      ]
+      ],
+      "ext": {
+        "consent": "consent"
+      }
     },
     "source": {
-      "schain": {
-        "complete": 0,
-        "nodes": [
-          {
-            "asi": "asi",
-            "sid": "sid"
-          }
-        ],
-        "ver": "1.0"
+      "ext": {
+        "schain": {
+          "complete": 0,
+          "nodes": [
+            {
+              "asi": "asi",
+              "sid": "sid"
+            }
+          ],
+          "ver": "1.0"
+        }
       }
+
     },
     "regs": {
-      "gdpr": 1,
-      "us_privacy": "us_privacy"
+      "ext": {
+        "gdpr": 1,
+        "us_privacy": "us_privacy"
+      }
     },
     "imp": [
       {
diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json
new file mode 100644
index 00000000..cdfaf37e
--- /dev/null
+++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json
@@ -0,0 +1,146 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {
+      "ip": "123.123.123.123"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {}
+        }
+      }
+    ],
+    "test": 1,
+    "tmax": 500
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "http://whatever.url",
+        "body": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {}
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.example.com",
+            "page": "http://www.example.com"
+          },
+          "device": {
+            "ip": "123.123.123.123"
+          },
+          "test": 1,
+          "tmax": 500
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "some-impression-id",
+                  "price": 1.234,
+                  "adm": "<script type=\"text/javascript\" src=\"http://some-url\"></script>",
+                  "adomain": [
+                    "some-adomain"
+                  ],
+                  "cid": "1234",
+                  "crid": "2345",
+                  "h": 1,
+                  "w": 1,
+                  "ext": {
+                    "prebid": {
+                      "type": "banner"
+                    }
+                  }
+                }
+              ],
+              "seat": "zeta_global_ssp"
+            }
+          ],
+          "bidid": "some-bidid",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "1",
+            "impid": "some-impression-id",
+            "price": 1.234,
+            "adm": "<script type=\"text/javascript\" src=\"http://some-url\"></script>",
+            "adomain": [
+              "some-adomain"
+            ],
+            "cid": "1234",
+            "crid": "2345",
+            "h": 1,
+            "w": 1,
+            "ext": {
+              "prebid": {
+                "type": "banner"
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json
new file mode 100644
index 00000000..68aabbed
--- /dev/null
+++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json
@@ -0,0 +1,89 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {
+      "ip": "123.123.123.123"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {}
+        }
+      }
+    ],
+    "test": 1,
+    "tmax": 500
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "http://whatever.url",
+        "body": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {}
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.example.com",
+            "page": "http://www.example.com"
+          },
+          "device": {
+            "ip": "123.123.123.123"
+          },
+          "test": 1,
+          "tmax": 500
+        }
+      },
+      "mockResponse": {
+        "status": 204
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json
new file mode 100644
index 00000000..248f4b04
--- /dev/null
+++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json
@@ -0,0 +1,132 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "tmax": 1000,
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "ext": {
+          "bidder": {}
+        },
+        "video": {
+          "w": 640,
+          "h": 480,
+          "mimes": [
+            "video/mp4"
+          ],
+          "placement": 1
+        }
+      }
+    ],
+    "test": 1,
+    "site": {
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {
+      "w": 1200,
+      "h": 900
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://whatever.url",
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "body": {
+          "id": "some-request-id",
+          "tmax": 1000,
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "ext": {
+                "bidder": {}
+              },
+              "video": {
+                "w": 640,
+                "h": 480,
+                "mimes": [
+                  "video/mp4"
+                ],
+                "placement": 1
+              }
+            }
+          ],
+          "site": {
+            "publisher": {
+              "id": "1"
+            },
+            "page": "http://www.example.com",
+            "domain": "www.example.com"
+          },
+          "device": {
+            "w": 1200,
+            "h": 900
+          },
+          "test": 1
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some-request-id",
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "some-impression-id",
+                  "crid": "some-creative-id",
+                  "adm": "<VAST>TAG</VAST>",
+                  "price": 20,
+                  "w": 640,
+                  "h": 480,
+                  "ext": {
+                    "prebid": {
+                      "type": "video"
+                    }
+                  }
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "1",
+            "impid": "some-impression-id",
+            "crid": "some-creative-id",
+            "adm": "<VAST>TAG</VAST>",
+            "price": 20,
+            "w": 640,
+            "h": 480,
+            "ext": {
+              "prebid": {
+                "type": "video"
+              }
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json
new file mode 100644
index 00000000..38f3bd32
--- /dev/null
+++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json
@@ -0,0 +1,95 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {
+      "ip": "123.123.123.123"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {}
+        }
+      }
+    ],
+    "test": 1,
+    "tmax": 500
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "http://whatever.url",
+        "body": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {}
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.example.com",
+            "page": "http://www.example.com"
+          },
+          "device": {
+            "ip": "123.123.123.123"
+          },
+          "test": 1,
+          "tmax": 500
+        }
+      },
+      "mockResponse": {
+        "status": 400
+      }
+    }
+  ],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json
new file mode 100644
index 00000000..070430ac
--- /dev/null
+++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json
@@ -0,0 +1,125 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {
+      "ip": "123.123.123.123"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {}
+        }
+      }
+    ],
+    "test": 1,
+    "tmax": 500
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "http://whatever.url",
+        "body": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {}
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.example.com",
+            "page": "http://www.example.com"
+          },
+          "device": {
+            "ip": "123.123.123.123"
+          },
+          "test": 1,
+          "tmax": 500
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "some-impression-id",
+                  "price": 1.234,
+                  "adm": "<script type=\"text/javascript\" src=\"http://some-url\"></script>",
+                  "adomain": [
+                    "some-adomain"
+                  ],
+                  "cid": "1234",
+                  "crid": "2345",
+                  "h": 1,
+                  "w": 1,
+                  "ext": {
+                    "prebid": {
+                      "type": "invalid"
+                    }
+                  }
+                }
+              ],
+              "seat": "zeta_global_ssp"
+            }
+          ],
+          "bidid": "some-bidid",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "invalid BidType: invalid",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json
new file mode 100644
index 00000000..e9ba895b
--- /dev/null
+++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json
@@ -0,0 +1,120 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {
+      "ip": "123.123.123.123"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {}
+        }
+      }
+    ],
+    "test": 1,
+    "tmax": 500
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "http://whatever.url",
+        "body": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {}
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.example.com",
+            "page": "http://www.example.com"
+          },
+          "device": {
+            "ip": "123.123.123.123"
+          },
+          "test": 1,
+          "tmax": 500
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "some-impression-id",
+                  "price": 1.234,
+                  "adm": "<script type=\"text/javascript\" src=\"http://some-url\"></script>",
+                  "adomain": [
+                    "some-adomain"
+                  ],
+                  "cid": "1234",
+                  "crid": "2345",
+                  "h": 1,
+                  "w": 1
+                }
+              ],
+              "seat": "zeta_global_ssp"
+            }
+          ],
+          "bidid": "some-bidid",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Failed to parse impression \"some-impression-id\" mediatype",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json
new file mode 100644
index 00000000..037c7307
--- /dev/null
+++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json
@@ -0,0 +1,95 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {
+      "ip": "123.123.123.123"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {}
+        }
+      }
+    ],
+    "test": 1,
+    "tmax": 500
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "http://whatever.url",
+        "body": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {}
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.example.com",
+            "page": "http://www.example.com"
+          },
+          "device": {
+            "ip": "123.123.123.123"
+          },
+          "test": 1,
+          "tmax": 500
+        }
+      },
+      "mockResponse": {
+        "status": 500
+      }
+    }
+  ],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/zeta_global_ssp/zeta_global_ssp_test.go b/adapters/zeta_global_ssp/zeta_global_ssp_test.go
new file mode 100644
index 00000000..3b7be288
--- /dev/null
+++ b/adapters/zeta_global_ssp/zeta_global_ssp_test.go
@@ -0,0 +1,21 @@
+package zeta_global_ssp
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderZetaGlobalSsp, config.Adapter{
+		Endpoint: "http://whatever.url"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "zeta_global_ssp-test", bidder)
+}
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 2d4341d0..8eb80672 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -1420,7 +1420,7 @@ var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBi
 	},
 }
 
-func (m *mockAmpExchange) HoldAuction(ctx context.Context, auctionRequest exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+func (m *mockAmpExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
 	r := auctionRequest.BidRequestWrapper
 	m.lastRequest = r.BidRequest
 
@@ -1459,7 +1459,7 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, auctionRequest exchan
 
 type mockAmpExchangeWarnings struct{}
 
-func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
 	response := &openrtb2.BidResponse{
 		SeatBid: []openrtb2.SeatBid{{
 			Bid: []openrtb2.Bid{{
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 8eda4de9..00c6d7f7 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -20,11 +20,13 @@ import (
 	"github.com/prebid/openrtb/v19/native1"
 	nativeRequests "github.com/prebid/openrtb/v19/native1/request"
 	"github.com/prebid/openrtb/v19/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/hooks/hookstage"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 
 	analyticsConf "github.com/prebid/prebid-server/analytics/config"
 	"github.com/prebid/prebid-server/config"
@@ -4305,13 +4307,10 @@ func TestParseRequestStoredResponses(t *testing.T) {
 			expectedErrorCount: 0,
 		},
 		{
-			name:             "req has two imps with missing stored responses",
-			givenRequestBody: validRequest(t, "req-two-imps-missing-stored-response.json"),
-			expectedStoredResponses: map[string]json.RawMessage{
-				"imp-id1": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`),
-				"imp-id2": json.RawMessage(nil),
-			},
-			expectedErrorCount: 0,
+			name:                    "req has two imps with missing stored responses",
+			givenRequestBody:        validRequest(t, "req-two-imps-missing-stored-response.json"),
+			expectedStoredResponses: nil,
+			expectedErrorCount:      2,
 		},
 		{
 			name:             "req has two imps: one with stored response and another imp without stored resp",
@@ -4404,13 +4403,10 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
 			expectedErrorCount: 0,
 		},
 		{
-			name:             "req has two imps with missing stored bid responses",
-			givenRequestBody: validRequest(t, "req-two-imps-missing-stored-bid-response.json"),
-			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
-				"imp-id1": {"testBidder1": nil},
-				"imp-id2": {"testBidder2": nil},
-			},
-			expectedErrorCount: 0,
+			name:                       "req has two imps with missing stored bid responses",
+			givenRequestBody:           validRequest(t, "req-two-imps-missing-stored-bid-response.json"),
+			expectedStoredBidResponses: nil,
+			expectedErrorCount:         2,
 		},
 	}
 	for _, test := range tests {
@@ -5341,3 +5337,12 @@ type mockStageExecutor struct {
 func (e mockStageExecutor) GetOutcomes() []hookexecution.StageOutcome {
 	return e.outcomes
 }
+
+func TestRecordResponsePreparationMetrics(t *testing.T) {
+	mbi := map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo{
+		openrtb_ext.BidderAppnexus: {Durations: []time.Duration{10, 15}, AfterMakeBidsStartTime: time.Now()},
+	}
+	mockMetricEngine := &metrics.MetricsEngineMock{}
+	mockMetricEngine.On("RecordOverheadTime", metrics.MakeAuctionResponse, mock.Anything)
+	recordResponsePreparationMetrics(mbi, mockMetricEngine)
+}
diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go
index 094250fa..0ad47982 100644
--- a/endpoints/openrtb2/test_utils.go
+++ b/endpoints/openrtb2/test_utils.go
@@ -94,7 +94,7 @@ type testConfigValues struct {
 
 type brokenExchange struct{}
 
-func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+func (e *brokenExchange) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
 	return nil, errors.New("Critical, unrecoverable error.")
 }
 
@@ -848,7 +848,7 @@ type mockExchange struct {
 	lastRequest *openrtb2.BidRequest
 }
 
-func (m *mockExchange) HoldAuction(ctx context.Context, auctionRequest exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+func (m *mockExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
 	r := auctionRequest.BidRequestWrapper
 	m.lastRequest = r.BidRequest
 	return &openrtb2.BidResponse{
@@ -885,8 +885,8 @@ type warningsCheckExchange struct {
 	auctionRequest exchange.AuctionRequest
 }
 
-func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
-	e.auctionRequest = r
+func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+	e.auctionRequest = *r
 	return nil, nil
 }
 
@@ -896,7 +896,7 @@ type nobidExchange struct {
 	gotRequest *openrtb2.BidRequest
 }
 
-func (e *nobidExchange) HoldAuction(ctx context.Context, auctionRequest exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+func (e *nobidExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
 	r := auctionRequest.BidRequestWrapper
 	e.gotRequest = r.BidRequest
 	return &openrtb2.BidResponse{
@@ -1181,7 +1181,7 @@ type exchangeTestWrapper struct {
 	actualValidatedBidReq *openrtb2.BidRequest
 }
 
-func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
 
 	// rebuild/resync the request in the request wrapper.
 	if err := r.BidRequestWrapper.RebuildRequest(); err != nil {
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 4ad69e53..b75a7b99 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -1354,7 +1354,7 @@ type mockExchangeVideo struct {
 	cache       *mockCacheClient
 }
 
-func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
 	if err := r.BidRequestWrapper.RebuildRequest(); err != nil {
 		return nil, err
 	}
@@ -1394,7 +1394,7 @@ type mockExchangeAppendBidderNames struct {
 	cache       *mockCacheClient
 }
 
-func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
 	m.lastRequest = r.BidRequestWrapper.BidRequest
 	if debugLog != nil && debugLog.Enabled {
 		m.cache.called = true
@@ -1430,7 +1430,7 @@ type mockExchangeVideoNoBids struct {
 	cache       *mockCacheClient
 }
 
-func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
 	m.lastRequest = r.BidRequestWrapper.BidRequest
 	return &openrtb2.BidResponse{
 		SeatBid: []openrtb2.SeatBid{{}},
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index dca2b4f7..2aa03573 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -108,7 +108,11 @@ func TestSingleBidder(t *testing.T) {
 			addCallSignHeader:   false,
 			bidAdjustments:      bidAdjustments,
 		}
-		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+		extraInfo := &adapters.ExtraRequestInfo{}
+		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+		assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations)
+		assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero())
+
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
 
@@ -230,7 +234,10 @@ func TestSingleBidderGzip(t *testing.T) {
 			addCallSignHeader:   false,
 			bidAdjustments:      bidAdjustments,
 		}
-		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+		extraInfo := &adapters.ExtraRequestInfo{}
+		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+		assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations)
+		assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero())
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
 
@@ -330,8 +337,8 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
-
+	extraInfo := &adapters.ExtraRequestInfo{}
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
 	expectedHttpCalls := []*openrtb_ext.ExtHttpCall{
 		{
 			Uri:            server.URL,
@@ -344,6 +351,8 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 
 	assert.Empty(t, errs)
 	assert.Len(t, seatBids, 1)
+	assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations)
+	assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero())
 	assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCalls)
 }
 
@@ -382,7 +391,8 @@ func TestSetGPCHeader(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	extraInfo := &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -396,6 +406,8 @@ func TestSetGPCHeader(t *testing.T) {
 
 	assert.Empty(t, errs)
 	assert.Len(t, seatBids, 1)
+	assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations)
+	assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero())
 	assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCall)
 }
 
@@ -432,7 +444,8 @@ func TestSetGPCHeaderNil(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	extraInfo := &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -445,6 +458,8 @@ func TestSetGPCHeaderNil(t *testing.T) {
 	}
 
 	assert.Empty(t, errs)
+	assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations)
+	assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero())
 	assert.Len(t, seatBids, 1)
 	assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCall)
 }
@@ -547,7 +562,7 @@ func TestBidderTimeout(t *testing.T) {
 	callInfo := bidder.doRequest(ctx, &adapters.RequestData{
 		Method: "POST",
 		Uri:    server.URL,
-	})
+	}, time.Now())
 	if callInfo.err == nil {
 		t.Errorf("The bidder should report an error if the context has expired already.")
 	}
@@ -566,7 +581,7 @@ func TestInvalidRequest(t *testing.T) {
 
 	callInfo := bidder.doRequest(context.Background(), &adapters.RequestData{
 		Method: "\"", // force http.NewRequest() to fail
-	})
+	}, time.Now())
 	if callInfo.err == nil {
 		t.Errorf("bidderAdapter.doRequest should return an error if the request data is malformed.")
 	}
@@ -590,7 +605,7 @@ func TestConnectionClose(t *testing.T) {
 	callInfo := bidder.doRequest(context.Background(), &adapters.RequestData{
 		Method: "POST",
 		Uri:    server.URL,
-	})
+	}, time.Now())
 	if callInfo.err == nil {
 		t.Errorf("bidderAdapter.doRequest should return an error if the connection closes unexpectedly.")
 	}
@@ -2004,15 +2019,16 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 		bidResponse: &adapters.BidderResponse{},
 	}
 
-	// setup a mock metrics engine and its expectation
-	metrics := &metrics.MetricsEngineMock{}
+	// setup a mock mockMetricEngine engine and its expectation
+	mockMetricEngine := &metrics.MetricsEngineMock{}
 	expectedAdapterName := openrtb_ext.BidderAppnexus
 	compareConnWaitTime := func(dur time.Duration) bool { return dur.Nanoseconds() > 0 }
 
-	metrics.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once()
+	mockMetricEngine.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once()
+	mockMetricEngine.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once()
 
 	// Run requestBid using an http.Client with a mock handler
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil, "")
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, mockMetricEngine, openrtb_ext.BidderAppnexus, nil, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2025,13 +2041,13 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	_, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	_, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{PbsEntryPoint: metrics.ReqTypeORTB2Web}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
 
 	// Assert no errors
 	assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
 
 	// Assert RecordAdapterConnections() was called with the parameters we expected
-	metrics.AssertExpectations(t)
+	mockMetricEngine.AssertExpectations(t)
 }
 
 type DNSDoneTripper struct{}
@@ -2070,6 +2086,7 @@ func TestCallRecordDNSTime(t *testing.T) {
 	// setup a mock metrics engine and its expectation
 	metricsMock := &metrics.MetricsEngineMock{}
 	metricsMock.Mock.On("RecordDNSTime", mock.Anything).Return()
+	metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once()
 
 	// Instantiate the bidder that will send the request. We'll make sure to use an
 	// http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{})
@@ -2081,7 +2098,7 @@ func TestCallRecordDNSTime(t *testing.T) {
 	}
 
 	// Run test
-	bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"})
+	bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"}, time.Now())
 
 	// Tried one or another, none seem to work without panicking
 	metricsMock.AssertExpectations(t)
@@ -2091,6 +2108,7 @@ func TestCallRecordTLSHandshakeTime(t *testing.T) {
 	// setup a mock metrics engine and its expectation
 	metricsMock := &metrics.MetricsEngineMock{}
 	metricsMock.Mock.On("RecordTLSHandshakeTime", mock.Anything).Return()
+	metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once()
 
 	// Instantiate the bidder that will send the request. We'll make sure to use an
 	// http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{})
@@ -2102,7 +2120,7 @@ func TestCallRecordTLSHandshakeTime(t *testing.T) {
 	}
 
 	// Run test
-	bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"})
+	bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"}, time.Now())
 
 	// Tried one or another, none seem to work without panicking
 	metricsMock.AssertExpectations(t)
@@ -2190,7 +2208,7 @@ func TestTimeoutNotificationOn(t *testing.T) {
 		loggerBuffer.WriteString(fmt.Sprintf(fmt.Sprintln(msg), args...))
 	}
 
-	bidderAdapter.doRequestImpl(ctx, &bidRequest, logger)
+	bidderAdapter.doRequestImpl(ctx, &bidRequest, logger, time.Now())
 
 	// Wait a little longer than the 205ms mock server sleep.
 	time.Sleep(210 * time.Millisecond)
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 3b2abb21..bef48be1 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -362,7 +362,7 @@ func TestDebugBehaviour(t *testing.T) {
 			bidRequest.Ext = nil
 		}
 
-		auctionRequest := AuctionRequest{
+		auctionRequest := &AuctionRequest{
 			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest},
 			Account:           config.Account{DebugAllow: test.debugData.accountLevelDebugAllowed},
 			UserSyncs:         &emptyUsersync{},
@@ -533,7 +533,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
 
 		bidRequest.Ext = json.RawMessage(`{"prebid":{"debug":true}}`)
 
-		auctionRequest := AuctionRequest{
+		auctionRequest := &AuctionRequest{
 			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest},
 			Account:           config.Account{DebugAllow: true},
 			UserSyncs:         &emptyUsersync{},
@@ -716,7 +716,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) {
 		// Set bidRequest currency list
 		mockBidRequest.Cur = []string{test.in.bidRequestCurrency}
 
-		auctionRequest := AuctionRequest{
+		auctionRequest := &AuctionRequest{
 			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
 			Account:           config.Account{},
 			UserSyncs:         &emptyUsersync{},
@@ -811,7 +811,7 @@ func TestAdapterCurrency(t *testing.T) {
 	}
 
 	// Run Auction
-	auctionRequest := AuctionRequest{
+	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: request},
 		Account:           config.Account{},
 		UserSyncs:         &emptyUsersync{},
@@ -984,7 +984,7 @@ func TestFloorsSignalling(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		auctionRequest := AuctionRequest{
+		auctionRequest := &AuctionRequest{
 			BidRequestWrapper: test.req,
 			Account:           config.Account{DebugAllow: true, PriceFloors: config.AccountPriceFloors{Enabled: test.floorsEnable, MaxRule: 100, MaxSchemaDims: 5}},
 			UserSyncs:         &emptyUsersync{},
@@ -1368,7 +1368,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 		for _, test := range testGroup.testCases {
 			mockBidRequest.Ext = test.inExt
 
-			auctionRequest := AuctionRequest{
+			auctionRequest := &AuctionRequest{
 				BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
 				Account:           config.Account{},
 				UserSyncs:         &emptyUsersync{},
@@ -2042,7 +2042,7 @@ func TestRaceIntegration(t *testing.T) {
 
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
-	auctionRequest := AuctionRequest{
+	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: getTestBuildRequest(t)},
 		Account:           config.Account{},
 		UserSyncs:         &emptyUsersync{},
@@ -2267,7 +2267,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 		}},
 	}
 
-	auctionRequest := AuctionRequest{
+	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: request},
 		Account:           config.Account{},
 		UserSyncs:         &emptyUsersync{},
@@ -2395,7 +2395,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 		impExtInfoMap[impID] = ImpExtInfo{}
 	}
 
-	auctionRequest := AuctionRequest{
+	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest},
 		Account: config.Account{
 			ID:            "testaccount",
@@ -4310,7 +4310,7 @@ func TestStoredAuctionResponses(t *testing.T) {
 
 	for _, test := range testCases {
 
-		auctionRequest := AuctionRequest{
+		auctionRequest := &AuctionRequest{
 			BidRequestWrapper:      &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
 			Account:                config.Account{},
 			UserSyncs:              &emptyUsersync{},
@@ -4646,7 +4646,7 @@ func TestAuctionDebugEnabled(t *testing.T) {
 		Test: 1,
 	}
 
-	auctionRequest := AuctionRequest{
+	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest},
 		Account:           config.Account{DebugAllow: false},
 		UserSyncs:         &emptyUsersync{},
@@ -4720,7 +4720,7 @@ func TestPassExperimentConfigsToHoldAuction(t *testing.T) {
 		Ext:  json.RawMessage(`{"prebid":{"experiment":{"adscert":{"enabled": true}}}}`),
 	}
 
-	auctionRequest := AuctionRequest{
+	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
 		Account:           config.Account{},
 		UserSyncs:         &emptyUsersync{},
@@ -5175,7 +5175,7 @@ func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) {
 
 		mockBidRequest.Ext = test.in.requestExt
 
-		auctionRequest := AuctionRequest{
+		auctionRequest := &AuctionRequest{
 			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
 			Account:           test.in.config.AccountDefaults,
 			UserSyncs:         &emptyUsersync{},
@@ -5595,7 +5595,7 @@ func TestModulesCanBeExecutedForMultipleBiddersSimultaneously(t *testing.T) {
 
 	exec := hookexecution.NewHookExecutor(TestApplyHookMutationsBuilder{}, "/openrtb2/auction", &metricsConfig.NilMetricsEngine{})
 
-	auctionRequest := AuctionRequest{
+	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest},
 		Account:           config.Account{DebugAllow: true},
 		UserSyncs:         &emptyUsersync{},
@@ -5678,3 +5678,10 @@ func (e mockUpdateBidRequestHook) HandleBidderRequestHook(_ context.Context, mct
 
 	return hookstage.HookResult[hookstage.BidderRequestPayload]{ChangeSet: c, ModuleContext: mctx.ModuleContext}, nil
 }
+
+func TestNilAuctionRequest(t *testing.T) {
+	ex := &exchange{}
+	response, err := ex.HoldAuction(context.Background(), nil, &DebugLog{})
+	assert.Nil(t, response)
+	assert.Nil(t, err)
+}
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 5ea508b9..49d8b5fa 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -125,7 +125,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 		req.Site = &openrtb2.Site{}
 	}
 
-	auctionRequest := AuctionRequest{
+	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req},
 		Account:           config.Account{},
 		UserSyncs:         &emptyUsersync{},
diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go
index 80aa2550..f77dcf00 100644
--- a/metrics/config/metrics_test.go
+++ b/metrics/config/metrics_test.go
@@ -34,7 +34,7 @@ func TestGoMetricsEngine(t *testing.T) {
 	testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys, modulesStages)
 	_, ok := testEngine.MetricsEngine.(*metrics.Metrics)
 	if !ok {
-		t.Error("Expected a legacy Metrics as MetricsEngine, but didn't get it")
+		t.Error("Expected a Metrics as MetricsEngine, but didn't get it")
 	}
 }
 
@@ -148,7 +148,6 @@ func TestMultiMetricsEngine(t *testing.T) {
 
 	//Make the metrics engine, instantiated here with goEngine, fill its RequestStatuses[RequestType][metrics.RequestStatusXX] with the new boolean values added to metrics.Labels
 	VerifyMetrics(t, "RequestStatuses.OpenRTB2.OK", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusOK].Count(), 5)
-	VerifyMetrics(t, "RequestStatuses.Legacy.OK", goEngine.RequestStatuses[metrics.ReqTypeLegacy][metrics.RequestStatusOK].Count(), 0)
 	VerifyMetrics(t, "RequestStatuses.AMP.OK", goEngine.RequestStatuses[metrics.ReqTypeAMP][metrics.RequestStatusOK].Count(), 0)
 	VerifyMetrics(t, "RequestStatuses.AMP.BlacklistedAcctOrApp", goEngine.RequestStatuses[metrics.ReqTypeAMP][metrics.RequestStatusBlacklisted].Count(), 1)
 	VerifyMetrics(t, "RequestStatuses.Video.OK", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusOK].Count(), 0)
diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go
index f5b860db..1b0b20e4 100644
--- a/metrics/go_metrics_test.go
+++ b/metrics/go_metrics_test.go
@@ -80,6 +80,10 @@ func TestNewMetrics(t *testing.T) {
 	ensureContains(t, registry, "ads_cert_requests.ok", m.AdsCertRequestsSuccess)
 	ensureContains(t, registry, "ads_cert_requests.failed", m.AdsCertRequestsFailure)
 
+	ensureContains(t, registry, "request_over_head_time.pre-bidder", m.OverheadTimer[PreBidder])
+	ensureContains(t, registry, "request_over_head_time.make-auction-response", m.OverheadTimer[MakeAuctionResponse])
+	ensureContains(t, registry, "request_over_head_time.make-bidder-requests", m.OverheadTimer[MakeBidderRequests])
+
 	for module, stages := range moduleStageNames {
 		for _, stage := range stages {
 			ensureContainsModuleMetrics(t, registry, fmt.Sprintf("modules.module.%s.stage.%s", module, stage), m.ModuleMetrics[module][stage])
@@ -1193,6 +1197,55 @@ func TestRecordAccountUpgradeStatusMetrics(t *testing.T) {
 	}
 }
 
+func TestRecordOverheadTime(t *testing.T) {
+	testCases := []struct {
+		name          string
+		time          time.Duration
+		overheadType  OverheadType
+		expectedCount int64
+		expectedSum   int64
+	}{
+		{
+			name:          "record-pre-bidder-overhead-time-1",
+			time:          time.Duration(500),
+			overheadType:  PreBidder,
+			expectedCount: 1,
+			expectedSum:   500,
+		},
+		{
+			name:          "record-pre-bidder-overhead-time-2",
+			time:          time.Duration(500),
+			overheadType:  PreBidder,
+			expectedCount: 2,
+			expectedSum:   1000,
+		},
+		{
+			name:          "record-auction-response-overhead-time",
+			time:          time.Duration(500),
+			overheadType:  MakeAuctionResponse,
+			expectedCount: 1,
+			expectedSum:   500,
+		},
+		{
+			name:          "record-make-bidder-requests-overhead-time",
+			time:          time.Duration(500),
+			overheadType:  MakeBidderRequests,
+			expectedCount: 1,
+			expectedSum:   500,
+		},
+	}
+	registry := metrics.NewRegistry()
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			m := NewMetrics(registry, []openrtb_ext.BidderName{}, config.DisabledMetrics{}, nil, nil)
+			m.RecordOverheadTime(test.overheadType, test.time)
+			overheadMetrics := m.OverheadTimer[test.overheadType]
+			assert.Equal(t, test.expectedCount, overheadMetrics.Count())
+			assert.Equal(t, test.expectedSum, overheadMetrics.Sum())
+		})
+	}
+}
+
 func ensureContainsBidTypeMetrics(t *testing.T, registry metrics.Registry, prefix string, mdm map[openrtb_ext.BidType]*MarkupDeliveryMetrics) {
 	ensureContains(t, registry, prefix+".banner.adm_bids_received", mdm[openrtb_ext.BidTypeBanner].AdmMeter)
 	ensureContains(t, registry, prefix+".banner.nurl_bids_received", mdm[openrtb_ext.BidTypeBanner].NurlMeter)
diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go
index 4cb3cd7f..d2ede844 100644
--- a/metrics/prometheus/prometheus_test.go
+++ b/metrics/prometheus/prometheus_test.go
@@ -55,7 +55,6 @@ func TestMetricCountGatekeeping(t *testing.T) {
 	// Calculate Per-Adapter Cardinality
 	adapterCount := len(openrtb_ext.CoreBidderNames())
 	perAdapterCardinalityCount := adapterCardinalityCount / adapterCount
-
 	// Verify General Cardinality
 	// - This assertion provides a warning for newly added high-cardinality non-adapter specific metrics. The hardcoded limit
 	//   is an arbitrary soft ceiling. Thought should be given as to the value of the new metrics if you find yourself
@@ -520,6 +519,52 @@ func TestRequestTimeMetric(t *testing.T) {
 	}
 }
 
+func TestRecordOverheadTimeMetric(t *testing.T) {
+	testCases := []struct {
+		description   string
+		overheadType  metrics.OverheadType
+		timeInMs      float64
+		expectedCount uint64
+		expectedSum   float64
+	}{
+		{
+			description:   "record-pre-bidder-overhead-time-1",
+			overheadType:  metrics.PreBidder,
+			timeInMs:      500,
+			expectedCount: 1,
+			expectedSum:   0.5,
+		},
+		{
+			description:   "record-pre-bidder-overhead-time-2",
+			overheadType:  metrics.PreBidder,
+			timeInMs:      400,
+			expectedCount: 2,
+			expectedSum:   0.9,
+		},
+		{
+			description:   "record-auction-response-overhead-time",
+			overheadType:  metrics.MakeAuctionResponse,
+			timeInMs:      500,
+			expectedCount: 1,
+			expectedSum:   0.5,
+		},
+		{
+			description:   "record-make-bidder-requests-overhead-time",
+			overheadType:  metrics.MakeBidderRequests,
+			timeInMs:      500,
+			expectedCount: 1,
+			expectedSum:   0.5,
+		},
+	}
+
+	metric := createMetricsForTesting()
+	for _, test := range testCases {
+		metric.RecordOverheadTime(test.overheadType, time.Duration(test.timeInMs)*time.Millisecond)
+		resultingHistogram := getHistogramFromHistogramVec(metric.overheadTimer, overheadTypeLabel, test.overheadType.String())
+		assertHistogram(t, test.description, resultingHistogram, test.expectedCount, test.expectedSum)
+	}
+}
+
 func TestRecordStoredDataFetchTime(t *testing.T) {
 	tests := []struct {
 		description string
diff --git a/stored_responses/stored_responses_test.go b/stored_responses/stored_responses_test.go
index 6c4960fe..c4ddea27 100644
--- a/stored_responses/stored_responses_test.go
+++ b/stored_responses/stored_responses_test.go
@@ -590,6 +590,213 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) {
 
 }
 
+func TestProcessStoredResponsesNotFoundResponse(t *testing.T) {
+	bidderMap := map[string]openrtb_ext.BidderName{"bidderA": "bidderA", "bidderB": "bidderB"}
+	bidStoredResp1 := json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "bidderA"}]`)
+	bidStoredResp2 := json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "bidderB"}]`)
+	mockStoredResponses := map[string]json.RawMessage{
+		"1": bidStoredResp1,
+		"2": bidStoredResp2,
+		"3": nil,
+		"4": nil,
+	}
+	fetcher := &mockStoredBidResponseFetcher{mockStoredResponses}
+
+	testCases := []struct {
+		description    string
+		requestJson    []byte
+		expectedErrors []error
+	}{
+		{
+			description: "Stored bid response with nil data, one bidder one imp",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderB", "id": "3"}
+                    		]
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedErrors: []error{
+				errors.New("failed to fetch stored bid response for impId = imp-id1, bidder = bidderB and storedBidResponse id = 3"),
+			},
+		},
+		{
+			description: "Stored bid response with nil data, one bidder, two imps, one with correct stored response",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderB", "id": "1"}
+                    		]
+                		}
+            		}
+    			  },
+                  {
+    			    "id": "imp-id2",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderB", "id": "3"}
+                    		]
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedErrors: []error{
+				errors.New("failed to fetch stored bid response for impId = imp-id2, bidder = bidderB and storedBidResponse id = 3"),
+			},
+		},
+		{
+			description: "Stored bid response with nil data, one bidder, two imps, both with correct stored response",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderB", "id": "4"}
+                    		]
+                		}
+            		}
+    			  },
+                  {
+    			    "id": "imp-id2",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderB", "id": "3"}
+                    		]
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedErrors: []error{
+				errors.New("failed to fetch stored bid response for impId = imp-id1, bidder = bidderB and storedBidResponse id = 4"),
+				errors.New("failed to fetch stored bid response for impId = imp-id2, bidder = bidderB and storedBidResponse id = 3"),
+			},
+		},
+		{
+			description: "Stored auction response with nil data and one imp",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "4"
+                    		}
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedErrors: []error{
+				errors.New("failed to fetch stored auction response for impId = imp-id1 and storedAuctionResponse id = 4"),
+			},
+		},
+		{
+			description: "Stored auction response with nil data, and two imps with nil responses",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "4"
+                    		}
+                		}
+            		}
+    			  },
+                  {
+    			    "id": "imp-id2",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "3"
+                    		}
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedErrors: []error{
+				errors.New("failed to fetch stored auction response for impId = imp-id1 and storedAuctionResponse id = 4"),
+				errors.New("failed to fetch stored auction response for impId = imp-id2 and storedAuctionResponse id = 3"),
+			},
+		},
+		{
+			description: "Stored auction response with nil data, two imps, one with nil responses",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "2"
+                    		}
+                		}
+            		}
+    			  },
+                  {
+    			    "id": "imp-id2",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "3"
+                    		}
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedErrors: []error{
+				errors.New("failed to fetch stored auction response for impId = imp-id2 and storedAuctionResponse id = 3"),
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		_, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap)
+		for _, err := range test.expectedErrors {
+			assert.Contains(t, errorList, err, "incorrect errors returned: %s", test.description)
+		}
+	}
+}
+
 func TestFlipMap(t *testing.T) {
 	testCases := []struct {
 		description              string