From 995cac32ca04a32f691aa4b7dade0985b063484b Mon Sep 17 00:00:00 2001 From: Nilesh Chate Date: Tue, 20 Feb 2024 14:47:17 +0530 Subject: [PATCH 1/4] cache rctx for in-app --- .../pubmatic/openwrap/beforevalidationhook.go | 12 +++++- modules/pubmatic/openwrap/cache.go | 43 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 modules/pubmatic/openwrap/cache.go diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index cd0b02d7f60..91fb71e0e5e 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -76,9 +76,19 @@ func (m OpenWrap) handleBeforeValidationHook( } rCtx.PubID = pubID rCtx.PubIDStr = strconv.Itoa(pubID) - rCtx.Source, rCtx.Origin = getSourceAndOrigin(payload.BidRequest) rCtx.PageURL = getPageURL(payload.BidRequest) + rCtx.Source, rCtx.Origin = getSourceAndOrigin(payload.BidRequest) rCtx.Platform = getPlatformFromRequest(payload.BidRequest) + + // todo: if platform == in-app + // todo: rctx.DisplayVersionID or seperate cache and make it ttl 30 min + if newRctx, ok := ow.getCachedRequest(rCtx); ok { + result.Warnings = append(result.Warnings, "using cached request context") + result.ModuleContext["rctx"] = newRctx + return result, nil + } + defer m.cacheRequest(rCtx) + rCtx.UA = getUserAgent(payload.BidRequest, rCtx.UA) rCtx.DeviceCtx.Platform = getDevicePlatform(rCtx, payload.BidRequest) populateDeviceContext(&rCtx.DeviceCtx, payload.BidRequest.Device) diff --git a/modules/pubmatic/openwrap/cache.go b/modules/pubmatic/openwrap/cache.go new file mode 100644 index 00000000000..0647305926d --- /dev/null +++ b/modules/pubmatic/openwrap/cache.go @@ -0,0 +1,43 @@ +package openwrap + +import ( + "fmt" + + "github.com/prebid/prebid-server/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func (ow *OpenWrap) cacheRequest(rctx models.RequestCtx) { + if rctx.IsTestRequest != 0 || rctx.ABTestConfig != 0 || rctx.ABTestConfigApplied != 0 || rctx.AdapterThrottleMap != nil || rctx.PageURL == "" { + return + } + + rctx.LoggerImpressionID = "" + rctx.IP = "" + rctx.StartTime = 0 + rctx.Trackers = nil + rctx.ResponseExt = openrtb_ext.ExtBidResponse{} + rctx.WinningBids = nil + rctx.DroppedBids = nil + rctx.DefaultBids = nil + rctx.SeatNonBids = nil + rctx.BidderResponseTimeMillis = nil + rctx.MatchedImpression = nil + rctx.CustomDimensions = nil +} + +func (ow *OpenWrap) getCachedRequest(rctx models.RequestCtx) (models.RequestCtx, bool) { + if rctx.Platform == models.PLATFORM_APP { + storedRequestKey := fmt.Sprintf("%s%s%d%s%s", rctx.PubIDStr, rctx.ProfileIDStr, rctx.VersionID, rctx.PageURL, rctx.Source) + storedRCtx, ok := ow.cache.Get(storedRequestKey) + if ok { + if newRctx, ok := storedRCtx.(models.RequestCtx); ok { + newRctx.StartTime = rctx.StartTime + newRctx.IP = rctx.IP + newRctx.LoggerImpressionID = rctx.LoggerImpressionID + return newRctx, true + } + } + } + return rctx, false +} From 6c718a63d088f6262fe3239278b1f1ba674f4641 Mon Sep 17 00:00:00 2001 From: Nilesh Chate Date: Tue, 20 Feb 2024 16:39:05 +0530 Subject: [PATCH 2/4] use app id for cache --- modules/pubmatic/openwrap/beforevalidationhook.go | 1 + modules/pubmatic/openwrap/cache.go | 8 +++++--- modules/pubmatic/openwrap/config/config.go | 1 + modules/pubmatic/openwrap/models/openwrap.go | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index 91fb71e0e5e..0152c32188e 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -79,6 +79,7 @@ func (m OpenWrap) handleBeforeValidationHook( rCtx.PageURL = getPageURL(payload.BidRequest) rCtx.Source, rCtx.Origin = getSourceAndOrigin(payload.BidRequest) rCtx.Platform = getPlatformFromRequest(payload.BidRequest) + rCtx.App = payload.BidRequest.App // todo: if platform == in-app // todo: rctx.DisplayVersionID or seperate cache and make it ttl 30 min diff --git a/modules/pubmatic/openwrap/cache.go b/modules/pubmatic/openwrap/cache.go index 0647305926d..d811474414e 100644 --- a/modules/pubmatic/openwrap/cache.go +++ b/modules/pubmatic/openwrap/cache.go @@ -8,7 +8,9 @@ import ( ) func (ow *OpenWrap) cacheRequest(rctx models.RequestCtx) { - if rctx.IsTestRequest != 0 || rctx.ABTestConfig != 0 || rctx.ABTestConfigApplied != 0 || rctx.AdapterThrottleMap != nil || rctx.PageURL == "" { + if !ow.cfg.Features.AppRequestCache || + rctx.IsTestRequest != 0 || rctx.ABTestConfig != 0 || rctx.ABTestConfigApplied != 0 || rctx.AdapterThrottleMap != nil || + rctx.PageURL == "" || rctx.App != nil || rctx.App.Bundle != "" || rctx.App.ID != "" { return } @@ -27,8 +29,8 @@ func (ow *OpenWrap) cacheRequest(rctx models.RequestCtx) { } func (ow *OpenWrap) getCachedRequest(rctx models.RequestCtx) (models.RequestCtx, bool) { - if rctx.Platform == models.PLATFORM_APP { - storedRequestKey := fmt.Sprintf("%s%s%d%s%s", rctx.PubIDStr, rctx.ProfileIDStr, rctx.VersionID, rctx.PageURL, rctx.Source) + if ow.cfg.Features.AppRequestCache && rctx.Platform == models.PLATFORM_APP { + storedRequestKey := fmt.Sprintf("%s%s%d%s%s%s", rctx.PubIDStr, rctx.ProfileIDStr, rctx.VersionID, rctx.PageURL, rctx.App.ID, rctx.App.Bundle) storedRCtx, ok := ow.cache.Get(storedRequestKey) if ok { if newRctx, ok := storedRCtx.(models.RequestCtx); ok { diff --git a/modules/pubmatic/openwrap/config/config.go b/modules/pubmatic/openwrap/config/config.go index b26d90fd2c5..a862d25ee23 100755 --- a/modules/pubmatic/openwrap/config/config.go +++ b/modules/pubmatic/openwrap/config/config.go @@ -81,6 +81,7 @@ type PixelView struct { } type FeatureToggle struct { + AppRequestCache bool } type Log struct { //Log Details diff --git a/modules/pubmatic/openwrap/models/openwrap.go b/modules/pubmatic/openwrap/models/openwrap.go index 1cad765346e..4d05e76e97a 100644 --- a/modules/pubmatic/openwrap/models/openwrap.go +++ b/modules/pubmatic/openwrap/models/openwrap.go @@ -56,6 +56,7 @@ type RequestCtx struct { //tracker PageURL string + App *openrtb2.App StartTime int64 DeviceCtx DeviceCtx From 58fa735cf23c89fe9a993884664736f1ec288e8e Mon Sep 17 00:00:00 2001 From: Nilesh Chate Date: Wed, 21 Feb 2024 16:11:12 +0530 Subject: [PATCH 3/4] fix condition --- modules/pubmatic/openwrap/cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pubmatic/openwrap/cache.go b/modules/pubmatic/openwrap/cache.go index d811474414e..884951d31a7 100644 --- a/modules/pubmatic/openwrap/cache.go +++ b/modules/pubmatic/openwrap/cache.go @@ -10,7 +10,7 @@ import ( func (ow *OpenWrap) cacheRequest(rctx models.RequestCtx) { if !ow.cfg.Features.AppRequestCache || rctx.IsTestRequest != 0 || rctx.ABTestConfig != 0 || rctx.ABTestConfigApplied != 0 || rctx.AdapterThrottleMap != nil || - rctx.PageURL == "" || rctx.App != nil || rctx.App.Bundle != "" || rctx.App.ID != "" { + rctx.PageURL == "" || rctx.App == nil || rctx.App.Bundle == "" || rctx.App.ID == "" { return } From a794c01c1e85e8c54b6603228a8c01e3b3edcc7e Mon Sep 17 00:00:00 2001 From: Nilesh Chate Date: Wed, 21 Feb 2024 16:47:05 +0530 Subject: [PATCH 4/4] fix cache reset --- .../pubmatic/openwrap/beforevalidationhook.go | 649 +++++++++--------- modules/pubmatic/openwrap/cache.go | 31 +- 2 files changed, 345 insertions(+), 335 deletions(-) diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index 0152c32188e..0c49ea909a1 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -85,400 +85,403 @@ func (m OpenWrap) handleBeforeValidationHook( // todo: rctx.DisplayVersionID or seperate cache and make it ttl 30 min if newRctx, ok := ow.getCachedRequest(rCtx); ok { result.Warnings = append(result.Warnings, "using cached request context") - result.ModuleContext["rctx"] = newRctx - return result, nil - } - defer m.cacheRequest(rCtx) - - rCtx.UA = getUserAgent(payload.BidRequest, rCtx.UA) - rCtx.DeviceCtx.Platform = getDevicePlatform(rCtx, payload.BidRequest) - populateDeviceContext(&rCtx.DeviceCtx, payload.BidRequest.Device) - - if rCtx.UidCookie == nil { - m.metricEngine.RecordUidsCookieNotPresentErrorStats(rCtx.PubIDStr, rCtx.ProfileIDStr) - } - m.metricEngine.RecordPublisherProfileRequests(rCtx.PubIDStr, rCtx.ProfileIDStr) + // result.ModuleContext["rctx"] = newRctx + // return result, nil + rCtx = newRctx + } else { + defer m.cacheRequest(rCtx) - requestExt, err := models.GetRequestExt(payload.BidRequest.Ext) - if err != nil { - result.NbrCode = nbr.InvalidRequestExt - err = errors.New("failed to get request ext: " + err.Error()) - result.Errors = append(result.Errors, err.Error()) - return result, err - } - rCtx.NewReqExt = requestExt - rCtx.CustomDimensions = customdimensions.GetCustomDimensions(requestExt.Prebid.BidderParams) - rCtx.ReturnAllBidStatus = requestExt.Prebid.ReturnAllBidStatus + rCtx.UA = getUserAgent(payload.BidRequest, rCtx.UA) + rCtx.DeviceCtx.Platform = getDevicePlatform(rCtx, payload.BidRequest) + populateDeviceContext(&rCtx.DeviceCtx, payload.BidRequest.Device) - // TODO: verify preference of request.test vs queryParam test ++ this check is only for the CTV requests - if payload.BidRequest.Test != 0 { - rCtx.IsTestRequest = payload.BidRequest.Test - } + if rCtx.UidCookie == nil { + m.metricEngine.RecordUidsCookieNotPresentErrorStats(rCtx.PubIDStr, rCtx.ProfileIDStr) + } + m.metricEngine.RecordPublisherProfileRequests(rCtx.PubIDStr, rCtx.ProfileIDStr) - partnerConfigMap, err := m.getProfileData(rCtx, *payload.BidRequest) - if err != nil || len(partnerConfigMap) == 0 { - // TODO: seperate DB fetch errors as internal errors - result.NbrCode = nbr.InvalidProfileConfiguration + requestExt, err := models.GetRequestExt(payload.BidRequest.Ext) if err != nil { - err = errors.New("failed to get profile data: " + err.Error()) - } else { - err = errors.New("failed to get profile data: received empty data") + result.NbrCode = nbr.InvalidRequestExt + err = errors.New("failed to get request ext: " + err.Error()) + result.Errors = append(result.Errors, err.Error()) + return result, err } - result.Errors = append(result.Errors, err.Error()) - m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) - m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) - return result, err - } - - rCtx.PartnerConfigMap = partnerConfigMap // keep a copy at module level as well - if ver, err := strconv.Atoi(models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.DisplayVersionID)); err == nil { - rCtx.DisplayVersionID = ver - } - platform := rCtx.GetVersionLevelKey(models.PLATFORM_KEY) - if platform == "" { - result.NbrCode = nbr.InvalidPlatform - err = errors.New("failed to get platform data") - result.Errors = append(result.Errors, err.Error()) - m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) - m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) - return result, err - } - rCtx.Platform = platform - rCtx.DeviceCtx.Platform = getDevicePlatform(rCtx, payload.BidRequest) - rCtx.SendAllBids = isSendAllBids(rCtx) - - m.metricEngine.RecordPublisherRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.Platform) - - if newPartnerConfigMap, ok := ABTestProcessing(rCtx); ok { - rCtx.ABTestConfigApplied = 1 - rCtx.PartnerConfigMap = newPartnerConfigMap - result.Warnings = append(result.Warnings, "update the rCtx.PartnerConfigMap with ABTest data") - } + rCtx.NewReqExt = requestExt + rCtx.CustomDimensions = customdimensions.GetCustomDimensions(requestExt.Prebid.BidderParams) + rCtx.ReturnAllBidStatus = requestExt.Prebid.ReturnAllBidStatus - //TMax should be updated after ABTest processing - rCtx.TMax = m.setTimeout(rCtx, payload.BidRequest) - - var allPartnersThrottledFlag bool - rCtx.AdapterThrottleMap, allPartnersThrottledFlag = GetAdapterThrottleMap(rCtx.PartnerConfigMap) - if allPartnersThrottledFlag { - result.NbrCode = nbr.AllPartnerThrottled - result.Errors = append(result.Errors, "All adapters throttled") - rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz - return result, err - } + // TODO: verify preference of request.test vs queryParam test ++ this check is only for the CTV requests + if payload.BidRequest.Test != 0 { + rCtx.IsTestRequest = payload.BidRequest.Test + } - priceGranularity, err := computePriceGranularity(rCtx) - if err != nil { - result.NbrCode = nbr.InvalidPriceGranularityConfig - err = errors.New("failed to price granularity details: " + err.Error()) - result.Errors = append(result.Errors, err.Error()) - rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz - return result, err - } - - rCtx.AdUnitConfig = m.cache.GetAdunitConfigFromCache(payload.BidRequest, rCtx.PubID, rCtx.ProfileID, rCtx.DisplayID) - - requestExt.Prebid.Debug = rCtx.Debug - // requestExt.Prebid.SupportDeals = rCtx.SupportDeals && rCtx.IsCTVRequest // TODO: verify usecase of Prefered deals vs Support details - requestExt.Prebid.AlternateBidderCodes, rCtx.MarketPlaceBidders = getMarketplaceBidders(requestExt.Prebid.AlternateBidderCodes, partnerConfigMap) - requestExt.Prebid.Targeting = &openrtb_ext.ExtRequestTargeting{ - PriceGranularity: &priceGranularity, - IncludeBidderKeys: boolutil.BoolPtr(true), - IncludeWinners: boolutil.BoolPtr(true), - } - - isAdPodRequest := false - disabledSlots := 0 - serviceSideBidderPresent := false - requestExt.Prebid.BidAdjustmentFactors = map[string]float64{} - - aliasgvlids := make(map[string]uint16) - for i := 0; i < len(payload.BidRequest.Imp); i++ { - slotType := "banner" - var adpodExt *models.AdPod - var isAdPodImpression bool - imp := payload.BidRequest.Imp[i] - - impExt := &models.ImpExtension{} - if len(imp.Ext) != 0 { - err := json.Unmarshal(imp.Ext, impExt) + partnerConfigMap, err := m.getProfileData(rCtx, *payload.BidRequest) + if err != nil || len(partnerConfigMap) == 0 { + // TODO: seperate DB fetch errors as internal errors + result.NbrCode = nbr.InvalidProfileConfiguration if err != nil { - result.NbrCode = nbr.InternalError - err = errors.New("failed to parse imp.ext: " + imp.ID) - result.Errors = append(result.Errors, err.Error()) - return result, err + err = errors.New("failed to get profile data: " + err.Error()) + } else { + err = errors.New("failed to get profile data: received empty data") } + result.Errors = append(result.Errors, err.Error()) + m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) + m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) + return result, err } - if rCtx.Endpoint == models.EndpointWebS2S { - imp.TagID = getTagID(imp, impExt) + + rCtx.PartnerConfigMap = partnerConfigMap // keep a copy at module level as well + if ver, err := strconv.Atoi(models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.DisplayVersionID)); err == nil { + rCtx.DisplayVersionID = ver } - if imp.TagID == "" { - result.NbrCode = nbr.InvalidImpressionTagID - err = errors.New("tagid missing for imp: " + imp.ID) + platform := rCtx.GetVersionLevelKey(models.PLATFORM_KEY) + if platform == "" { + result.NbrCode = nbr.InvalidPlatform + err = errors.New("failed to get platform data") result.Errors = append(result.Errors, err.Error()) + m.metricEngine.RecordPublisherInvalidProfileRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.ProfileIDStr) + m.metricEngine.RecordPublisherInvalidProfileImpressions(rCtx.PubIDStr, rCtx.ProfileIDStr, len(payload.BidRequest.Imp)) return result, err } + rCtx.Platform = platform + rCtx.DeviceCtx.Platform = getDevicePlatform(rCtx, payload.BidRequest) + rCtx.SendAllBids = isSendAllBids(rCtx) - if imp.Video != nil { - slotType = "video" - - //add stats for video instl impressions - if imp.Instl == 1 { - m.metricEngine.RecordVideoInstlImpsStats(rCtx.PubIDStr, rCtx.ProfileIDStr) - } - if len(requestExt.Prebid.Macros) == 0 { - // provide custom macros for video event trackers - requestExt.Prebid.Macros = getVASTEventMacros(rCtx) - } + m.metricEngine.RecordPublisherRequests(rCtx.Endpoint, rCtx.PubIDStr, rCtx.Platform) - if rCtx.IsCTVRequest && imp.Video.Ext != nil { - if _, _, _, err := jsonparser.Get(imp.Video.Ext, "adpod"); err == nil { - isAdPodImpression = true - if !isAdPodRequest { - isAdPodRequest = true - rCtx.MetricsEngine.RecordCTVReqCountWithAdPod(rCtx.PubIDStr, rCtx.ProfileIDStr) - } - } - } + if newPartnerConfigMap, ok := ABTestProcessing(rCtx); ok { + rCtx.ABTestConfigApplied = 1 + rCtx.PartnerConfigMap = newPartnerConfigMap + result.Warnings = append(result.Warnings, "update the rCtx.PartnerConfigMap with ABTest data") } - div := "" - if impExt.Wrapper != nil { - div = impExt.Wrapper.Div - } + //TMax should be updated after ABTest processing + rCtx.TMax = m.setTimeout(rCtx, payload.BidRequest) - // reuse the existing impExt instead of allocating a new one - reward := impExt.Reward - if reward != nil { - impExt.Prebid.IsRewardedInventory = reward + var allPartnersThrottledFlag bool + rCtx.AdapterThrottleMap, allPartnersThrottledFlag = GetAdapterThrottleMap(rCtx.PartnerConfigMap) + if allPartnersThrottledFlag { + result.NbrCode = nbr.AllPartnerThrottled + result.Errors = append(result.Errors, "All adapters throttled") + rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz + return result, err } - // if imp.ext.data.pbadslot is absent then set it to tagId - if len(impExt.Data.PbAdslot) == 0 { - impExt.Data.PbAdslot = imp.TagID + + priceGranularity, err := computePriceGranularity(rCtx) + if err != nil { + result.NbrCode = nbr.InvalidPriceGranularityConfig + err = errors.New("failed to price granularity details: " + err.Error()) + result.Errors = append(result.Errors, err.Error()) + rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) // for wrapper logger sz + return result, err } - incomingSlots := getIncomingSlots(imp) - slotName := getSlotName(imp.TagID, impExt) - adUnitName := getAdunitName(imp.TagID, impExt) + rCtx.AdUnitConfig = m.cache.GetAdunitConfigFromCache(payload.BidRequest, rCtx.PubID, rCtx.ProfileID, rCtx.DisplayID) - var videoAdUnitCtx, bannerAdUnitCtx models.AdUnitCtx - if rCtx.AdUnitConfig != nil { - // Currently we are supporting Video config via Ad Unit config file for in-app / video / display profiles - if (rCtx.Platform == models.PLATFORM_APP || rCtx.Platform == models.PLATFORM_VIDEO || rCtx.Platform == models.PLATFORM_DISPLAY) && imp.Video != nil { - if payload.BidRequest.App != nil && payload.BidRequest.App.Content != nil { - m.metricEngine.RecordReqImpsWithContentCount(rCtx.PubIDStr, models.ContentTypeApp) - } - if payload.BidRequest.Site != nil && payload.BidRequest.Site.Content != nil { - m.metricEngine.RecordReqImpsWithContentCount(rCtx.PubIDStr, models.ContentTypeSite) + requestExt.Prebid.Debug = rCtx.Debug + // requestExt.Prebid.SupportDeals = rCtx.SupportDeals && rCtx.IsCTVRequest // TODO: verify usecase of Prefered deals vs Support details + requestExt.Prebid.AlternateBidderCodes, rCtx.MarketPlaceBidders = getMarketplaceBidders(requestExt.Prebid.AlternateBidderCodes, partnerConfigMap) + requestExt.Prebid.Targeting = &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: &priceGranularity, + IncludeBidderKeys: boolutil.BoolPtr(true), + IncludeWinners: boolutil.BoolPtr(true), + } + + isAdPodRequest := false + disabledSlots := 0 + serviceSideBidderPresent := false + requestExt.Prebid.BidAdjustmentFactors = map[string]float64{} + + aliasgvlids := make(map[string]uint16) + for i := 0; i < len(payload.BidRequest.Imp); i++ { + slotType := "banner" + var adpodExt *models.AdPod + var isAdPodImpression bool + imp := payload.BidRequest.Imp[i] + + impExt := &models.ImpExtension{} + if len(imp.Ext) != 0 { + err := json.Unmarshal(imp.Ext, impExt) + if err != nil { + result.NbrCode = nbr.InternalError + err = errors.New("failed to parse imp.ext: " + imp.ID) + result.Errors = append(result.Errors, err.Error()) + return result, err } } - videoAdUnitCtx = adunitconfig.UpdateVideoObjectWithAdunitConfig(rCtx, imp, div, payload.BidRequest.Device.ConnectionType) - bannerAdUnitCtx = adunitconfig.UpdateBannerObjectWithAdunitConfig(rCtx, imp, div) - } + if rCtx.Endpoint == models.EndpointWebS2S { + imp.TagID = getTagID(imp, impExt) + } + if imp.TagID == "" { + result.NbrCode = nbr.InvalidImpressionTagID + err = errors.New("tagid missing for imp: " + imp.ID) + result.Errors = append(result.Errors, err.Error()) + return result, err + } + + if imp.Video != nil { + slotType = "video" - // ignore adunit config status for native as it is not supported for native - if (!isSlotEnabled(videoAdUnitCtx, bannerAdUnitCtx)) && imp.Native == nil { - disabledSlots++ + //add stats for video instl impressions + if imp.Instl == 1 { + m.metricEngine.RecordVideoInstlImpsStats(rCtx.PubIDStr, rCtx.ProfileIDStr) + } + if len(requestExt.Prebid.Macros) == 0 { + // provide custom macros for video event trackers + requestExt.Prebid.Macros = getVASTEventMacros(rCtx) + } - rCtx.ImpBidCtx[imp.ID] = models.ImpCtx{ // for wrapper logger sz - IncomingSlots: incomingSlots, - AdUnitName: adUnitName, - SlotName: slotName, - IsRewardInventory: reward, + if rCtx.IsCTVRequest && imp.Video.Ext != nil { + if _, _, _, err := jsonparser.Get(imp.Video.Ext, "adpod"); err == nil { + isAdPodImpression = true + if !isAdPodRequest { + isAdPodRequest = true + rCtx.MetricsEngine.RecordCTVReqCountWithAdPod(rCtx.PubIDStr, rCtx.ProfileIDStr) + } + } + } } - continue - } - bidderMeta := make(map[string]models.PartnerData) - nonMapped := make(map[string]struct{}) - for _, partnerConfig := range rCtx.PartnerConfigMap { - if partnerConfig[models.SERVER_SIDE_FLAG] != "1" { - continue + div := "" + if impExt.Wrapper != nil { + div = impExt.Wrapper.Div } - partneridstr, ok := partnerConfig[models.PARTNER_ID] - if !ok { - continue + // reuse the existing impExt instead of allocating a new one + reward := impExt.Reward + if reward != nil { + impExt.Prebid.IsRewardedInventory = reward } - partnerID, err := strconv.Atoi(partneridstr) - if err != nil || partnerID == models.VersionLevelConfigID { - continue + // if imp.ext.data.pbadslot is absent then set it to tagId + if len(impExt.Data.PbAdslot) == 0 { + impExt.Data.PbAdslot = imp.TagID } - // bidderCode is in context with pubmatic. Ex. it could be appnexus-1, appnexus-2, etc. - bidderCode := partnerConfig[models.BidderCode] - // prebidBidderCode is equivalent of PBS-Core's bidderCode - prebidBidderCode := partnerConfig[models.PREBID_PARTNER_NAME] - // - rCtx.PrebidBidderCode[prebidBidderCode] = bidderCode + incomingSlots := getIncomingSlots(imp) + slotName := getSlotName(imp.TagID, impExt) + adUnitName := getAdunitName(imp.TagID, impExt) - if _, ok := rCtx.AdapterThrottleMap[bidderCode]; ok { - result.Warnings = append(result.Warnings, "Dropping throttled adapter from auction: "+bidderCode) - continue + var videoAdUnitCtx, bannerAdUnitCtx models.AdUnitCtx + if rCtx.AdUnitConfig != nil { + // Currently we are supporting Video config via Ad Unit config file for in-app / video / display profiles + if (rCtx.Platform == models.PLATFORM_APP || rCtx.Platform == models.PLATFORM_VIDEO || rCtx.Platform == models.PLATFORM_DISPLAY) && imp.Video != nil { + if payload.BidRequest.App != nil && payload.BidRequest.App.Content != nil { + m.metricEngine.RecordReqImpsWithContentCount(rCtx.PubIDStr, models.ContentTypeApp) + } + if payload.BidRequest.Site != nil && payload.BidRequest.Site.Content != nil { + m.metricEngine.RecordReqImpsWithContentCount(rCtx.PubIDStr, models.ContentTypeSite) + } + } + videoAdUnitCtx = adunitconfig.UpdateVideoObjectWithAdunitConfig(rCtx, imp, div, payload.BidRequest.Device.ConnectionType) + bannerAdUnitCtx = adunitconfig.UpdateBannerObjectWithAdunitConfig(rCtx, imp, div) } - var isRegex bool - var slot, kgpv string - var bidderParams json.RawMessage - var matchedSlotKeysVAST []string - switch prebidBidderCode { - case string(openrtb_ext.BidderPubmatic), models.BidderPubMaticSecondaryAlias: - slot, kgpv, isRegex, bidderParams, err = bidderparams.PreparePubMaticParamsV25(rCtx, m.cache, *payload.BidRequest, imp, *impExt, partnerID) - case models.BidderVASTBidder: - slot, bidderParams, matchedSlotKeysVAST, err = bidderparams.PrepareVASTBidderParams(rCtx, m.cache, *payload.BidRequest, imp, *impExt, partnerID, adpodExt) - default: - slot, kgpv, isRegex, bidderParams, err = bidderparams.PrepareAdapterParamsV25(rCtx, m.cache, *payload.BidRequest, imp, *impExt, partnerID) + // ignore adunit config status for native as it is not supported for native + if (!isSlotEnabled(videoAdUnitCtx, bannerAdUnitCtx)) && imp.Native == nil { + disabledSlots++ + + rCtx.ImpBidCtx[imp.ID] = models.ImpCtx{ // for wrapper logger sz + IncomingSlots: incomingSlots, + AdUnitName: adUnitName, + SlotName: slotName, + IsRewardInventory: reward, + } + continue } - if err != nil || len(bidderParams) == 0 { - result.Errors = append(result.Errors, fmt.Sprintf("no bidder params found for imp:%s partner: %s", imp.ID, prebidBidderCode)) - nonMapped[bidderCode] = struct{}{} - m.metricEngine.RecordPartnerConfigErrors(rCtx.PubIDStr, rCtx.ProfileIDStr, bidderCode, models.PartnerErrSlotNotMapped) + bidderMeta := make(map[string]models.PartnerData) + nonMapped := make(map[string]struct{}) + for _, partnerConfig := range rCtx.PartnerConfigMap { + if partnerConfig[models.SERVER_SIDE_FLAG] != "1" { + continue + } - if prebidBidderCode != string(openrtb_ext.BidderPubmatic) && prebidBidderCode != string(models.BidderPubMaticSecondaryAlias) { + partneridstr, ok := partnerConfig[models.PARTNER_ID] + if !ok { + continue + } + partnerID, err := strconv.Atoi(partneridstr) + if err != nil || partnerID == models.VersionLevelConfigID { continue } - } - m.metricEngine.RecordPlatformPublisherPartnerReqStats(rCtx.Platform, rCtx.PubIDStr, bidderCode) + // bidderCode is in context with pubmatic. Ex. it could be appnexus-1, appnexus-2, etc. + bidderCode := partnerConfig[models.BidderCode] + // prebidBidderCode is equivalent of PBS-Core's bidderCode + prebidBidderCode := partnerConfig[models.PREBID_PARTNER_NAME] + // + rCtx.PrebidBidderCode[prebidBidderCode] = bidderCode - bidderMeta[bidderCode] = models.PartnerData{ - PartnerID: partnerID, - PrebidBidderCode: prebidBidderCode, - MatchedSlot: slot, // KGPSV - Params: bidderParams, - KGP: rCtx.PartnerConfigMap[partnerID][models.KEY_GEN_PATTERN], // acutual slot - KGPV: kgpv, // regex pattern, use this field for pubmatic default unmapped slot as well using isRegex - IsRegex: isRegex, // regex pattern - } + if _, ok := rCtx.AdapterThrottleMap[bidderCode]; ok { + result.Warnings = append(result.Warnings, "Dropping throttled adapter from auction: "+bidderCode) + continue + } - for _, bidder := range matchedSlotKeysVAST { - bidderMeta[bidder].VASTTagFlags[bidder] = false - } + var isRegex bool + var slot, kgpv string + var bidderParams json.RawMessage + var matchedSlotKeysVAST []string + switch prebidBidderCode { + case string(openrtb_ext.BidderPubmatic), models.BidderPubMaticSecondaryAlias: + slot, kgpv, isRegex, bidderParams, err = bidderparams.PreparePubMaticParamsV25(rCtx, m.cache, *payload.BidRequest, imp, *impExt, partnerID) + case models.BidderVASTBidder: + slot, bidderParams, matchedSlotKeysVAST, err = bidderparams.PrepareVASTBidderParams(rCtx, m.cache, *payload.BidRequest, imp, *impExt, partnerID, adpodExt) + default: + slot, kgpv, isRegex, bidderParams, err = bidderparams.PrepareAdapterParamsV25(rCtx, m.cache, *payload.BidRequest, imp, *impExt, partnerID) + } + + if err != nil || len(bidderParams) == 0 { + result.Errors = append(result.Errors, fmt.Sprintf("no bidder params found for imp:%s partner: %s", imp.ID, prebidBidderCode)) + nonMapped[bidderCode] = struct{}{} + m.metricEngine.RecordPartnerConfigErrors(rCtx.PubIDStr, rCtx.ProfileIDStr, bidderCode, models.PartnerErrSlotNotMapped) - isAlias := false - if alias, ok := partnerConfig[models.IsAlias]; ok && alias == "1" { - if prebidPartnerName, ok := partnerConfig[models.PREBID_PARTNER_NAME]; ok { - rCtx.Aliases[bidderCode] = adapters.ResolveOWBidder(prebidPartnerName) + if prebidBidderCode != string(openrtb_ext.BidderPubmatic) && prebidBidderCode != string(models.BidderPubMaticSecondaryAlias) { + continue + } + } + + m.metricEngine.RecordPlatformPublisherPartnerReqStats(rCtx.Platform, rCtx.PubIDStr, bidderCode) + + bidderMeta[bidderCode] = models.PartnerData{ + PartnerID: partnerID, + PrebidBidderCode: prebidBidderCode, + MatchedSlot: slot, // KGPSV + Params: bidderParams, + KGP: rCtx.PartnerConfigMap[partnerID][models.KEY_GEN_PATTERN], // acutual slot + KGPV: kgpv, // regex pattern, use this field for pubmatic default unmapped slot as well using isRegex + IsRegex: isRegex, // regex pattern + } + + for _, bidder := range matchedSlotKeysVAST { + bidderMeta[bidder].VASTTagFlags[bidder] = false + } + + isAlias := false + if alias, ok := partnerConfig[models.IsAlias]; ok && alias == "1" { + if prebidPartnerName, ok := partnerConfig[models.PREBID_PARTNER_NAME]; ok { + rCtx.Aliases[bidderCode] = adapters.ResolveOWBidder(prebidPartnerName) + isAlias = true + } + } + if alias, ok := IsAlias(bidderCode); ok { + rCtx.Aliases[bidderCode] = alias isAlias = true } + + if isAlias || partnerConfig[models.PREBID_PARTNER_NAME] == models.BidderVASTBidder { + updateAliasGVLIds(aliasgvlids, bidderCode, partnerConfig) + } + + revShare := models.GetRevenueShare(rCtx.PartnerConfigMap[partnerID]) + requestExt.Prebid.BidAdjustmentFactors[bidderCode] = models.GetBidAdjustmentValue(revShare) + serviceSideBidderPresent = true + } // for(rctx.PartnerConfigMap + + // update the imp.ext with bidder params for this + if impExt.Prebid.Bidder == nil { + impExt.Prebid.Bidder = make(map[string]json.RawMessage) } - if alias, ok := IsAlias(bidderCode); ok { - rCtx.Aliases[bidderCode] = alias - isAlias = true + for bidder, meta := range bidderMeta { + impExt.Prebid.Bidder[bidder] = meta.Params } - if isAlias || partnerConfig[models.PREBID_PARTNER_NAME] == models.BidderVASTBidder { - updateAliasGVLIds(aliasgvlids, bidderCode, partnerConfig) + impExt.Wrapper = nil + impExt.Reward = nil + impExt.Bidder = nil + newImpExt, err := json.Marshal(impExt) + if err != nil { + result.Errors = append(result.Errors, fmt.Sprintf("failed to update bidder params for impression %s", imp.ID)) } - revShare := models.GetRevenueShare(rCtx.PartnerConfigMap[partnerID]) - requestExt.Prebid.BidAdjustmentFactors[bidderCode] = models.GetBidAdjustmentValue(revShare) - serviceSideBidderPresent = true - } // for(rctx.PartnerConfigMap + // cache the details for further processing + if _, ok := rCtx.ImpBidCtx[imp.ID]; !ok { + rCtx.ImpBidCtx[imp.ID] = models.ImpCtx{ + ImpID: imp.ID, + TagID: imp.TagID, + Div: div, + IsRewardInventory: reward, + BidFloor: imp.BidFloor, + BidFloorCur: imp.BidFloorCur, + Type: slotType, + Banner: imp.Banner != nil, + Video: imp.Video, + Native: imp.Native, + IncomingSlots: incomingSlots, + Bidders: make(map[string]models.PartnerData), + BidCtx: make(map[string]models.BidCtx), + NewExt: json.RawMessage(newImpExt), + IsAdPodRequest: isAdPodRequest, + SlotName: slotName, + AdUnitName: adUnitName, + } + } - // update the imp.ext with bidder params for this - if impExt.Prebid.Bidder == nil { - impExt.Prebid.Bidder = make(map[string]json.RawMessage) - } - for bidder, meta := range bidderMeta { - impExt.Prebid.Bidder[bidder] = meta.Params - } + if isAdPodImpression { + bidderMeta[string(openrtb_ext.BidderOWPrebidCTV)] = models.PartnerData{} + } - impExt.Wrapper = nil - impExt.Reward = nil - impExt.Bidder = nil - newImpExt, err := json.Marshal(impExt) - if err != nil { - result.Errors = append(result.Errors, fmt.Sprintf("failed to update bidder params for impression %s", imp.ID)) + impCtx := rCtx.ImpBidCtx[imp.ID] + impCtx.Bidders = bidderMeta + impCtx.NonMapped = nonMapped + impCtx.VideoAdUnitCtx = videoAdUnitCtx + impCtx.BannerAdUnitCtx = bannerAdUnitCtx + rCtx.ImpBidCtx[imp.ID] = impCtx + } // for(imp + + if disabledSlots == len(payload.BidRequest.Imp) { + result.NbrCode = nbr.AllSlotsDisabled + if err != nil { + err = errors.New("All slots disabled: " + err.Error()) + } else { + err = errors.New("All slots disabled") + } + result.Errors = append(result.Errors, err.Error()) + return result, nil } - // cache the details for further processing - if _, ok := rCtx.ImpBidCtx[imp.ID]; !ok { - rCtx.ImpBidCtx[imp.ID] = models.ImpCtx{ - ImpID: imp.ID, - TagID: imp.TagID, - Div: div, - IsRewardInventory: reward, - BidFloor: imp.BidFloor, - BidFloorCur: imp.BidFloorCur, - Type: slotType, - Banner: imp.Banner != nil, - Video: imp.Video, - Native: imp.Native, - IncomingSlots: incomingSlots, - Bidders: make(map[string]models.PartnerData), - BidCtx: make(map[string]models.BidCtx), - NewExt: json.RawMessage(newImpExt), - IsAdPodRequest: isAdPodRequest, - SlotName: slotName, - AdUnitName: adUnitName, + if !serviceSideBidderPresent { + result.NbrCode = nbr.ServerSidePartnerNotConfigured + if err != nil { + err = errors.New("server side partner not found: " + err.Error()) + } else { + err = errors.New("server side partner not found") } + result.Errors = append(result.Errors, err.Error()) + return result, nil } - if isAdPodImpression { - bidderMeta[string(openrtb_ext.BidderOWPrebidCTV)] = models.PartnerData{} + if cto := setContentTransparencyObject(rCtx, requestExt); cto != nil { + requestExt.Prebid.Transparency = cto } - impCtx := rCtx.ImpBidCtx[imp.ID] - impCtx.Bidders = bidderMeta - impCtx.NonMapped = nonMapped - impCtx.VideoAdUnitCtx = videoAdUnitCtx - impCtx.BannerAdUnitCtx = bannerAdUnitCtx - rCtx.ImpBidCtx[imp.ID] = impCtx - } // for(imp + adunitconfig.UpdateFloorsExtObjectFromAdUnitConfig(rCtx, requestExt) + setFloorsExt(requestExt, rCtx.PartnerConfigMap) - if disabledSlots == len(payload.BidRequest.Imp) { - result.NbrCode = nbr.AllSlotsDisabled - if err != nil { - err = errors.New("All slots disabled: " + err.Error()) - } else { - err = errors.New("All slots disabled") + if len(rCtx.Aliases) != 0 && requestExt.Prebid.Aliases == nil { + requestExt.Prebid.Aliases = make(map[string]string) } - result.Errors = append(result.Errors, err.Error()) - return result, nil - } - - if !serviceSideBidderPresent { - result.NbrCode = nbr.ServerSidePartnerNotConfigured - if err != nil { - err = errors.New("server side partner not found: " + err.Error()) - } else { - err = errors.New("server side partner not found") + for k, v := range rCtx.Aliases { + requestExt.Prebid.Aliases[k] = v } - result.Errors = append(result.Errors, err.Error()) - return result, nil - } - if cto := setContentTransparencyObject(rCtx, requestExt); cto != nil { - requestExt.Prebid.Transparency = cto - } - - adunitconfig.UpdateFloorsExtObjectFromAdUnitConfig(rCtx, requestExt) - setFloorsExt(requestExt, rCtx.PartnerConfigMap) + requestExt.Prebid.AliasGVLIDs = aliasgvlids + if _, ok := rCtx.AdapterThrottleMap[string(openrtb_ext.BidderPubmatic)]; !ok { + requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(openrtb_ext.BidderPubmatic)) + } - if len(rCtx.Aliases) != 0 && requestExt.Prebid.Aliases == nil { - requestExt.Prebid.Aliases = make(map[string]string) - } - for k, v := range rCtx.Aliases { - requestExt.Prebid.Aliases[k] = v - } + if _, ok := requestExt.Prebid.Aliases[string(models.BidderPubMaticSecondaryAlias)]; ok { + if _, ok := rCtx.AdapterThrottleMap[string(models.BidderPubMaticSecondaryAlias)]; !ok { + requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(models.BidderPubMaticSecondaryAlias)) + } + } - requestExt.Prebid.AliasGVLIDs = aliasgvlids - if _, ok := rCtx.AdapterThrottleMap[string(openrtb_ext.BidderPubmatic)]; !ok { - requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(openrtb_ext.BidderPubmatic)) - } + // similar to impExt, reuse the existing requestExt to avoid additional memory requests + requestExt.Wrapper = nil + requestExt.Bidder = nil - if _, ok := requestExt.Prebid.Aliases[string(models.BidderPubMaticSecondaryAlias)]; ok { - if _, ok := rCtx.AdapterThrottleMap[string(models.BidderPubMaticSecondaryAlias)]; !ok { - requestExt.Prebid.BidderParams, _ = updateRequestExtBidderParamsPubmatic(requestExt.Prebid.BidderParams, rCtx.Cookies, rCtx.LoggerImpressionID, string(models.BidderPubMaticSecondaryAlias)) - } } - // similar to impExt, reuse the existing requestExt to avoid additional memory requests - requestExt.Wrapper = nil - requestExt.Bidder = nil - if rCtx.Debug { newImp, _ := json.Marshal(rCtx.ImpBidCtx) result.DebugMessages = append(result.DebugMessages, "new imp: "+string(newImp)) diff --git a/modules/pubmatic/openwrap/cache.go b/modules/pubmatic/openwrap/cache.go index 884951d31a7..35cd2c67f83 100644 --- a/modules/pubmatic/openwrap/cache.go +++ b/modules/pubmatic/openwrap/cache.go @@ -3,6 +3,7 @@ package openwrap import ( "fmt" + "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -14,24 +15,26 @@ func (ow *OpenWrap) cacheRequest(rctx models.RequestCtx) { return } - rctx.LoggerImpressionID = "" - rctx.IP = "" rctx.StartTime = 0 - rctx.Trackers = nil + rctx.IP = "" + rctx.LoggerImpressionID = "" + rctx.Trackers = make(map[string]models.OWTracker) rctx.ResponseExt = openrtb_ext.ExtBidResponse{} - rctx.WinningBids = nil - rctx.DroppedBids = nil - rctx.DefaultBids = nil - rctx.SeatNonBids = nil - rctx.BidderResponseTimeMillis = nil - rctx.MatchedImpression = nil - rctx.CustomDimensions = nil + rctx.WinningBids = make(map[string]models.OwBid) + rctx.DroppedBids = make(map[string][]openrtb2.Bid) + rctx.DefaultBids = make(map[string]map[string][]openrtb2.Bid) + rctx.SeatNonBids = make(map[string][]openrtb_ext.NonBid) + rctx.BidderResponseTimeMillis = make(map[string]int) + rctx.MatchedImpression = make(map[string]int) + rctx.CustomDimensions = make(map[string]models.CustomDimension) + + ow.cache.Set(ow.getCachedRequestKey(rctx), rctx) } func (ow *OpenWrap) getCachedRequest(rctx models.RequestCtx) (models.RequestCtx, bool) { if ow.cfg.Features.AppRequestCache && rctx.Platform == models.PLATFORM_APP { - storedRequestKey := fmt.Sprintf("%s%s%d%s%s%s", rctx.PubIDStr, rctx.ProfileIDStr, rctx.VersionID, rctx.PageURL, rctx.App.ID, rctx.App.Bundle) - storedRCtx, ok := ow.cache.Get(storedRequestKey) + + storedRCtx, ok := ow.cache.Get(ow.getCachedRequestKey(rctx)) if ok { if newRctx, ok := storedRCtx.(models.RequestCtx); ok { newRctx.StartTime = rctx.StartTime @@ -43,3 +46,7 @@ func (ow *OpenWrap) getCachedRequest(rctx models.RequestCtx) (models.RequestCtx, } return rctx, false } + +func (ow *OpenWrap) getCachedRequestKey(rctx models.RequestCtx) string { + return fmt.Sprintf("%s%s%d%s%s%s", rctx.PubIDStr, rctx.ProfileIDStr, rctx.VersionID, rctx.PageURL, rctx.App.ID, rctx.App.Bundle) +}