Skip to content

Commit 4042992

Browse files
committed
Account config for limiting number of impressions
1 parent 67042e5 commit 4042992

7 files changed

Lines changed: 120 additions & 17 deletions

File tree

docs/application-settings.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ There are two ways to configure application settings: database and file. This do
1212
- `auction.truncate-target-attr` - Maximum targeting attributes size. Values between 1 and 255.
1313
- `auction.default-integration` - Default integration to assume.
1414
- `auction.debug-allow` - enables debug output in the auction response. Default `true`.
15+
- `auction.impression-limit` - a max number of impressions allowed for the auction, impressions that exceed this limit will be dropped, 0 means no limit.
1516
- `auction.bid-validations.banner-creative-max-size` - Overrides creative max size validation for banners. Valid values
1617
are:
1718
- "skip": don't do anything about creative max size for this publisher

src/main/java/org/prebid/server/metric/MetricName.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public enum MetricName {
3131
request_time,
3232
prices,
3333
imps_requested,
34+
imps_dropped,
3435
imps_banner,
3536
imps_video,
3637
imps_native,

src/main/java/org/prebid/server/metric/Metrics.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ public void updateAppAndNoCookieAndImpsRequestedMetrics(boolean isApp, boolean l
182182
incCounter(MetricName.imps_requested, numImps);
183183
}
184184

185+
public void updateImpsDroppedMetric(int numImps) {
186+
incCounter(MetricName.imps_dropped, numImps);
187+
}
188+
185189
public void updateImpTypesMetrics(List<Imp> imps) {
186190

187191
final Map<String, Long> mediaTypeToCount = imps.stream()

src/main/java/org/prebid/server/settings/model/AccountAuctionConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,7 @@ public class AccountAuctionConfig {
6060
AccountCacheConfig cache;
6161

6262
AccountBidRankingConfig ranking;
63+
64+
@JsonAlias("impression-limit")
65+
Integer impressionLimit;
6366
}

src/main/java/org/prebid/server/validation/RequestValidator.java

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.prebid.server.proto.openrtb.ext.request.ImpMediaType;
4747
import org.prebid.server.proto.openrtb.ext.response.BidType;
4848
import org.prebid.server.settings.model.Account;
49+
import org.prebid.server.settings.model.AccountAuctionConfig;
4950
import org.prebid.server.util.HttpUtil;
5051
import org.prebid.server.validation.model.ValidationResult;
5152

@@ -153,28 +154,25 @@ public ValidationResult validate(Account account,
153154
validateSchains(extRequestPrebid.getSchains());
154155
}
155156

156-
if (CollectionUtils.isEmpty(bidRequest.getImp())) {
157-
throw new ValidationException("request.imp must contain at least one element");
158-
}
159-
160157
final List<Imp> imps = bidRequest.getImp();
161-
final List<String> errors = new ArrayList<>();
162-
final Map<String, Integer> uniqueImps = new HashMap<>();
163-
for (int i = 0; i < imps.size(); i++) {
164-
final String impId = imps.get(i).getId();
165-
if (uniqueImps.get(impId) != null) {
166-
errors.add("request.imp[%d].id and request.imp[%d].id are both \"%s\". Imp IDs must be unique."
167-
.formatted(uniqueImps.get(impId), i, impId));
168-
}
169-
170-
uniqueImps.put(impId, i);
158+
if (CollectionUtils.isEmpty(imps)) {
159+
throw new ValidationException("request.imp must contain at least one element");
171160
}
172161

173-
if (CollectionUtils.isNotEmpty(errors)) {
174-
throw new ValidationException(String.join(System.lineSeparator(), errors));
162+
BidRequest requestWithImpsDropped = bidRequest;
163+
final int impsLimit = Optional.ofNullable(account)
164+
.map(Account::getAuction)
165+
.map(AccountAuctionConfig::getImpressionLimit)
166+
.orElse(0);
167+
if (impsLimit > 0 && imps.size() > impsLimit) {
168+
metrics.updateImpsDroppedMetric(imps.size() - impsLimit);
169+
warnings.add(("Only first %d impressions were kept due to the limit, "
170+
+ "all the subsequent impressions have been dropped for the auction").formatted(impsLimit));
171+
requestWithImpsDropped = bidRequest.toBuilder().imp(imps.stream().limit(impsLimit).toList()).build();
175172
}
176173

177-
impValidator.validateImps(bidRequest.getImp(), aliases, warnings);
174+
validateUniqueImps(requestWithImpsDropped.getImp());
175+
impValidator.validateImps(requestWithImpsDropped.getImp(), aliases, warnings);
178176

179177
final List<String> channels = new ArrayList<>();
180178
Optional.ofNullable(bidRequest.getApp()).ifPresent(ignored -> channels.add("request.app"));
@@ -361,6 +359,24 @@ private void validateSchains(List<ExtRequestPrebidSchain> schains) throws Valida
361359
}
362360
}
363361

362+
private static void validateUniqueImps(List<Imp> imps) throws ValidationException {
363+
final List<String> errors = new ArrayList<>();
364+
final Map<String, Integer> uniqueImps = new HashMap<>();
365+
for (int i = 0; i < imps.size(); i++) {
366+
final String impId = imps.get(i).getId();
367+
if (uniqueImps.get(impId) != null) {
368+
errors.add("request.imp[%d].id and request.imp[%d].id are both \"%s\". Imp IDs must be unique."
369+
.formatted(uniqueImps.get(impId), i, impId));
370+
}
371+
372+
uniqueImps.put(impId, i);
373+
}
374+
375+
if (CollectionUtils.isNotEmpty(errors)) {
376+
throw new ValidationException(String.join(System.lineSeparator(), errors));
377+
}
378+
}
379+
364380
private void validateExtBidPrebidData(ExtRequestPrebidData data,
365381
Map<String, String> aliases,
366382
boolean isDebugEnabled,

src/test/java/org/prebid/server/metric/MetricsTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,15 @@ public void updateAppAndNoCookieAndImpsRequestedMetricsShouldIncrementMetrics()
332332
assertThat(metricRegistry.counter("imps_requested").getCount()).isEqualTo(4);
333333
}
334334

335+
@Test
336+
public void updateImpsDroppedMetricShouldIncrementMetrics() {
337+
// when
338+
metrics.updateImpsDroppedMetric(2);
339+
340+
// then
341+
assertThat(metricRegistry.counter("imps_dropped").getCount()).isEqualTo(2);
342+
}
343+
335344
@Test
336345
public void updateDebugRequestsMetricsShouldIncrementMetrics() {
337346
// when

src/test/java/org/prebid/server/validation/RequestValidatorTest.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.prebid.server.proto.openrtb.ext.request.ExtUserPrebid;
4646
import org.prebid.server.proto.openrtb.ext.request.ImpMediaType;
4747
import org.prebid.server.settings.model.Account;
48+
import org.prebid.server.settings.model.AccountAuctionConfig;
4849
import org.prebid.server.validation.model.ValidationResult;
4950

5051
import java.math.BigDecimal;
@@ -61,11 +62,13 @@
6162
import static java.util.Collections.singletonMap;
6263
import static org.assertj.core.api.Assertions.assertThat;
6364
import static org.mockito.ArgumentMatchers.any;
65+
import static org.mockito.ArgumentMatchers.anyInt;
6466
import static org.mockito.ArgumentMatchers.eq;
6567
import static org.mockito.BDDMockito.given;
6668
import static org.mockito.Mock.Strictness.LENIENT;
6769
import static org.mockito.Mockito.doAnswer;
6870
import static org.mockito.Mockito.doThrow;
71+
import static org.mockito.Mockito.never;
6972
import static org.mockito.Mockito.verify;
7073

7174
@ExtendWith(MockitoExtension.class)
@@ -562,6 +565,72 @@ public void validateShouldReturnValidationMessageWhenRequestHaveDuplicatedImpIds
562565
.containsOnly("request.imp[0].id and request.imp[1].id are both \"11\". Imp IDs must be unique.");
563566
}
564567

568+
@Test
569+
public void validateShouldDropImpressionsOverAccountLimitAndReturnWarning() throws ValidationException {
570+
// given
571+
final Imp imp1 = Imp.builder().id("1").build();
572+
final Imp imp2 = Imp.builder().id("2").build();
573+
final BidRequest bidRequest = validBidRequestBuilder().imp(asList(imp1, imp2)).build();
574+
575+
final Account givenAccount = Account.builder()
576+
.id(ACCOUNT_ID)
577+
.auction(AccountAuctionConfig.builder().impressionLimit(1).build())
578+
.build();
579+
580+
// when
581+
final ValidationResult result = target.validate(givenAccount, bidRequest, null, null);
582+
583+
// then
584+
assertThat(result.getWarnings()).hasSize(1)
585+
.containsOnly("Only first 1 impressions were kept due to the limit, "
586+
+ "all the subsequent impressions have been dropped for the auction");
587+
assertThat(result.getErrors()).isEmpty();
588+
589+
verify(metrics).updateImpsDroppedMetric(1);
590+
verify(impValidator).validateImps(eq(singletonList(imp1)), any(), any());
591+
}
592+
593+
@Test
594+
public void validateShouldNotDropImpressionsReturnWarningWhenAccountLimitIsSetToZero() throws ValidationException {
595+
// given
596+
final Imp imp1 = Imp.builder().id("1").build();
597+
final Imp imp2 = Imp.builder().id("2").build();
598+
final BidRequest bidRequest = validBidRequestBuilder().imp(asList(imp1, imp2)).build();
599+
600+
final Account givenAccount = Account.builder()
601+
.id(ACCOUNT_ID)
602+
.auction(AccountAuctionConfig.builder().impressionLimit(0).build())
603+
.build();
604+
605+
// when
606+
final ValidationResult result = target.validate(givenAccount, bidRequest, null, null);
607+
608+
// then
609+
assertThat(result.getWarnings()).isEmpty();
610+
assertThat(result.getErrors()).isEmpty();
611+
612+
verify(metrics, never()).updateImpsDroppedMetric(anyInt());
613+
verify(impValidator).validateImps(eq(bidRequest.getImp()), any(), any());
614+
}
615+
616+
@Test
617+
public void validateShouldNotDropImpressionsReturnWarningWhenAccountLimitIsNotSet() throws ValidationException {
618+
// given
619+
final Imp imp1 = Imp.builder().id("1").build();
620+
final Imp imp2 = Imp.builder().id("2").build();
621+
final BidRequest bidRequest = validBidRequestBuilder().imp(asList(imp1, imp2)).build();
622+
623+
// when
624+
final ValidationResult result = target.validate(Account.empty(ACCOUNT_ID), bidRequest, null, null);
625+
626+
// then
627+
assertThat(result.getWarnings()).isEmpty();
628+
assertThat(result.getErrors()).isEmpty();
629+
630+
verify(metrics, never()).updateImpsDroppedMetric(anyInt());
631+
verify(impValidator).validateImps(eq(bidRequest.getImp()), any(), any());
632+
}
633+
565634
@Test
566635
public void validateShouldNotReturnValidationMessageIfUserExtIsEmptyJsonObject() {
567636
// given

0 commit comments

Comments
 (0)