From 8cd19b354249938d7cb257b697bfa123c4dd8b3f Mon Sep 17 00:00:00 2001 From: pm-nilesh-chate Date: Thu, 26 May 2022 09:06:45 +0000 Subject: [PATCH] Support adapters to set bid seat #2174 --- adapters/bidder.go | 2 ++ adapters/pubmatic/pubmatic.go | 6 ++++ exchange/bidder.go | 55 +++++++++++++++++++++++++------- exchange/bidder_validate_bids.go | 12 ++++--- exchange/exchange.go | 54 ++++++++++++++++++------------- openrtb_ext/bid.go | 1 + 6 files changed, 91 insertions(+), 39 deletions(-) diff --git a/adapters/bidder.go b/adapters/bidder.go index e60227777bf..a54b8ff2e41 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -93,12 +93,14 @@ func NewBidderResponse() *BidderResponse { // TypedBid.BidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response. // TypedBid.BidVideo will become "response.seatbid[i].bid.ext.prebid.video" in the final OpenRTB response. // TypedBid.DealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns. +// TypedBid.Seat new seat under which the bid should pe placed. Default is adapter name type TypedBid struct { Bid *openrtb2.Bid BidMeta *openrtb_ext.ExtBidPrebidMeta BidType openrtb_ext.BidType BidVideo *openrtb_ext.ExtBidPrebidVideo DealPriority int + Seat openrtb_ext.BidderName } // RequestData and ResponseData exist so that prebid-server core code can implement its "debug" functionality diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 00c9c906f96..c031d9e7114 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -24,6 +24,7 @@ type PubmaticAdapter struct { type pubmaticBidExt struct { BidType *int `json:"BidType,omitempty"` VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` + Marketplace string `json:"marketplace,omitempty"` } type pubmaticWrapperExt struct { @@ -398,10 +399,15 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa } bidType = getBidType(bidExt) } + seat := "" + if bidExt.Marketplace != "" { + seat = bidExt.Marketplace + } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bid, BidType: bidType, BidVideo: impVideo, + Seat: openrtb_ext.BidderName(seat), }) } diff --git a/exchange/bidder.go b/exchange/bidder.go index 74a239981b7..79b6ed94bd1 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -51,7 +51,7 @@ type AdaptedBidder interface { // // Any errors will be user-facing in the API. // Error messages should help publishers understand what might account for "bad" bids. - requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) + requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) ([]*pbsOrtbSeatBid, []error) } const ImpIdReqBody = "Stored bid response for impression id: " @@ -93,6 +93,8 @@ type pbsOrtbSeatBid struct { // 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 + // seat defines whom these extra bids belong to. + seat string } // AdaptBidder converts an adapters.Bidder into an exchange.AdaptedBidder. @@ -134,7 +136,7 @@ type bidderAdapterConfig struct { DebugInfo config.DebugInfo } -func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) ([]*pbsOrtbSeatBid, []error) { var reqData []*adapters.RequestData var errs []error @@ -193,10 +195,13 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } defaultCurrency := "USD" - seatBid := &pbsOrtbSeatBid{ - bids: make([]*pbsOrtbBid, 0, dataLen), - currency: defaultCurrency, - httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, dataLen), + seatBidMap := map[openrtb_ext.BidderName]*pbsOrtbSeatBid{ + bidderRequest.BidderName: { + bids: make([]*pbsOrtbBid, 0, dataLen), + currency: defaultCurrency, + httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, dataLen), + seat: string(bidderRequest.BidderName), + }, } // If the bidder made multiple requests, we still want them to enter as many bids as possible... @@ -209,11 +214,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde // - account debug is allowed // - bidder debug is allowed if headerDebugAllowed { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo)) } else { if accountDebugAllowed { if bidder.config.DebugInfo.Allow { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo)) } else { debugDisabledWarning := errortypes.Warning{ WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, @@ -244,7 +249,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde var err error for _, bidReqCur := range bidderRequest.BidRequest.Cur { if conversionRate, err = conversions.GetRate(bidResponse.Currency, bidReqCur); err == nil { - seatBid.currency = bidReqCur + seatBidMap[bidderRequest.BidderName].currency = bidReqCur break } } @@ -288,7 +293,29 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * bidAdjustment * conversionRate } - seatBid.bids = append(seatBid.bids, &pbsOrtbBid{ + + if bidResponse.Bids[i].BidMeta == nil { + bidResponse.Bids[i].BidMeta = &openrtb_ext.ExtBidPrebidMeta{} + } + bidResponse.Bids[i].BidMeta.AdapterCode = bidderRequest.BidderName.String() + + bidderName := bidderRequest.BidderName + if bidResponse.Bids[i].Seat != "" { + bidderName = bidResponse.Bids[i].Seat + + if _, ok := seatBidMap[bidderName]; !ok { + // Initalize seatBidMap entry as this is first extra bid with seat bidderName + // seatBidMap[bidderName] = + seatBidMap[bidderName] = &pbsOrtbSeatBid{ + bids: make([]*pbsOrtbBid, 0, dataLen), + currency: defaultCurrency, + // Do we need to fill httpCalls for this?. Can we refer one from adaptercode for debugging? + httpCalls: seatBidMap[bidderRequest.BidderName].httpCalls, + seat: bidderName.String(), + } + } + } + seatBidMap[bidderName].bids = append(seatBidMap[bidderName].bids, &pbsOrtbBid{ bid: bidResponse.Bids[i].Bid, bidMeta: bidResponse.Bids[i].BidMeta, bidType: bidResponse.Bids[i].BidType, @@ -307,7 +334,13 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde errs = append(errs, httpInfo.err) } } - return seatBid, errs + + seatBids := make([]*pbsOrtbSeatBid, 0, len(seatBidMap)) + for _, seatBid := range seatBidMap { + seatBids = append(seatBids, seatBid) + } + + return seatBids, errs } func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeResponse.Response, []error) { diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index ab1d3904c8e..72976f8a4ed 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -27,12 +27,14 @@ type validatedBidder struct { bidder AdaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { - seatBid, errs := v.bidder.requestBid(ctx, bidderRequest, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed) - if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { - errs = append(errs, validationErrors...) +func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) ([]*pbsOrtbSeatBid, []error) { + seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed) + for _, seatBid := range seatBids { + if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { + errs = append(errs, validationErrors...) + } } - return seatBid, errs + return seatBids, errs } // validateBids will run some validation checks on the returned bids and excise any invalid bids diff --git a/exchange/exchange.go b/exchange/exchange.go index 375273891ee..c0aa52246a0 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -78,9 +78,9 @@ type seatResponseExtra struct { } type bidResponseWrapper struct { - adapterBids *pbsOrtbSeatBid - adapterExtra *seatResponseExtra - bidder openrtb_ext.BidderName + adapterSeatBids []*pbsOrtbSeatBid + adapterExtra *seatResponseExtra + bidder openrtb_ext.BidderName } type BidIDGenerator interface { @@ -517,31 +517,33 @@ func (e *exchange) getAllBids( reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader - bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) + seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) // Add in time reporting elapsed := time.Since(start) - brw.adapterBids = bids + brw.adapterSeatBids = seatBids // Structure to record extra tracking data generated during bidding ae := new(seatResponseExtra) ae.ResponseTimeMillis = int(elapsed / time.Millisecond) - if bids != nil { - ae.HttpCalls = bids.httpCalls + if len(seatBids) != 0 { + ae.HttpCalls = seatBids[0].httpCalls } // Timing statistics e.me.RecordAdapterTime(bidderRequest.BidderLabels, time.Since(start)) - bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterBids) + bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterSeatBids) bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err) // Append any bid validation errors to the error list ae.Errors = errsToBidderErrors(err) ae.Warnings = errsToBidderWarnings(err) brw.adapterExtra = ae - if bids != nil { - for _, bid := range bids.bids { - var cpm = float64(bid.bid.Price * 1000) - e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm) - e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.bidType, bid.bid.AdM != "") + for _, seatBid := range seatBids { + if seatBid != nil { + for _, bid := range seatBid.bids { + var cpm = float64(bid.bid.Price * 1000) + e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm) + e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.bidType, bid.bid.AdM != "") + } } } chBids <- brw @@ -553,8 +555,13 @@ func (e *exchange) getAllBids( brw := <-chBids //if bidder returned no bids back - remove bidder from further processing - if brw.adapterBids != nil && len(brw.adapterBids.bids) != 0 { - adapterBids[brw.bidder] = brw.adapterBids + for _, seatBid := range brw.adapterSeatBids { + if seatBid != nil && len(seatBid.bids) != 0 { + // Do we need to address duplicate names here?. What if different BidderNames have exta bids with same seat value. + // Ex. 'pubmatic' adapter has extra bids under new 'groupm' seat and 'appnexus' also comes with additional bids under seat 'groupm' + // Above example might be more relatable with 'allowUnknownBidderCodes' + adapterBids[openrtb_ext.BidderName(seatBid.seat)] = seatBid + } } //but we need to add all bidders data to adapterExtra to have metrics and other metadata adapterExtra[brw.bidder] = brw.adapterExtra @@ -598,8 +605,9 @@ func (e *exchange) recoverSafely(bidderRequests []BidderRequest, } } -func bidsToMetric(bids *pbsOrtbSeatBid) metrics.AdapterBid { - if bids == nil || len(bids.bids) == 0 { +func bidsToMetric(seatBids []*pbsOrtbSeatBid) metrics.AdapterBid { + + if len(seatBids) == 0 || seatBids[0] == nil || len(seatBids[0].bids) == 0 { return metrics.AdapterBidNone } return metrics.AdapterBidPresent @@ -654,7 +662,7 @@ func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage { } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester -func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, errList []error) (*openrtb2.BidResponse, error) { +func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, errList []error) (*openrtb2.BidResponse, error) { bidResponse := new(openrtb2.BidResponse) var err error @@ -667,12 +675,12 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ // Create the SeatBids. We use a zero sized slice so that we can append non-zero seat bids, and not include seatBid // objects for seatBids without any bids. Preallocate the max possible size to avoid reallocating the array as we go. seatBids := make([]openrtb2.SeatBid, 0, len(liveAdapters)) - for _, a := range liveAdapters { + for a, adapterSeatBids := range adapterSeatBids { //while processing every single bib, do we need to handle categories here? - if adapterBids[a] != nil && len(adapterBids[a].bids) > 0 { - sb := e.makeSeatBid(adapterBids[a], a, adapterExtra, auc, returnCreative, impExtInfoMap) + if adapterSeatBids != nil && len(adapterSeatBids.bids) > 0 { + sb := e.makeSeatBid(adapterSeatBids, a, adapterExtra, auc, returnCreative, impExtInfoMap) seatBids = append(seatBids, *sb) - bidResponse.Cur = adapterBids[a].currency + bidResponse.Cur = adapterSeatBids.currency } } @@ -1180,7 +1188,7 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag } else { //create new seat bid and add it to live adapters liveAdapters = append(liveAdapters, bidderName) - newSeatBid := pbsOrtbSeatBid{bidsToAdd, "", nil} + newSeatBid := pbsOrtbSeatBid{bidsToAdd, "", nil, ""} adapterBids[bidderName] = &newSeatBid } diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 1b72b25bd92..8a6b88016bc 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -53,6 +53,7 @@ type ExtBidPrebidMeta struct { NetworkName string `json:"networkName,omitempty"` PrimaryCategoryID string `json:"primaryCatId,omitempty"` SecondaryCategoryIDs []string `json:"secondaryCatIds,omitempty"` + AdapterCode string `json:"adaptercode"` } // ExtBidPrebidVideo defines the contract for bidresponse.seatbid.bid[i].ext.prebid.video