Skip to content

Commit ba33fac

Browse files
tmax adjustments for individual adapters (#3615)
1 parent a6c719e commit ba33fac

File tree

17 files changed

+192
-50
lines changed

17 files changed

+192
-50
lines changed

docs/config-app.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ There are several typical keys:
153153
- `adapters.<BIDDER_NAME>.usersync.type` - usersync type (i.e. redirect, iframe).
154154
- `adapters.<BIDDER_NAME>.usersync.support-cors` - flag signals if CORS supported by usersync.
155155
- `adapters.<BIDDER_NAME>.debug.allow` - enables debug output in the auction response for the given bidder. Default `true`.
156+
- `adapters.<BIDDER_NAME>.tmax-deduction-ms` - adjusts the tmax sent to the bidder by deducting the provided value (ms). Default `0 ms` - no deduction.
156157

157158
In addition, each bidder could have arbitrary aliases configured that will look and act very much the same as the bidder itself.
158159
Aliases are configured by adding child configuration object at `adapters.<BIDDER_NAME>.aliases.<BIDDER_ALIAS>.`, aliases

extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHookTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,8 @@ private static BidderInfo bidderInfo(OrtbVersion ortbVersion) {
276276
false,
277277
false,
278278
null,
279-
Ortb.of(false));
279+
Ortb.of(false),
280+
0L);
280281
}
281282

282283
private static BidRequest emptyRequest() {

src/main/java/org/prebid/server/auction/ExchangeService.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,7 @@ private Future<BidderResponse> requestBids(BidderRequest bidderRequest,
12171217
final String bidderName = bidderRequest.getBidder();
12181218
final String resolvedBidderName = aliases.resolveBidder(bidderName);
12191219
final Bidder<?> bidder = bidderCatalog.bidderByName(resolvedBidderName);
1220+
final long bidderTmaxDeductionMs = bidderCatalog.bidderInfoByName(resolvedBidderName).getTmaxDeductionMs();
12201221
final BidRejectionTracker bidRejectionTracker = auctionContext.getBidRejectionTrackers().get(bidderName);
12211222

12221223
final TimeoutContext timeoutContext = auctionContext.getTimeoutContext();
@@ -1225,7 +1226,8 @@ private Future<BidderResponse> requestBids(BidderRequest bidderRequest,
12251226
final long bidderRequestStartTime = clock.millis();
12261227

12271228
return Future.succeededFuture(bidderRequest.getBidRequest())
1228-
.map(bidRequest -> adjustTmax(bidRequest, auctionStartTime, adjustmentFactor, bidderRequestStartTime))
1229+
.map(bidRequest -> adjustTmax(
1230+
bidRequest, auctionStartTime, adjustmentFactor, bidderRequestStartTime, bidderTmaxDeductionMs))
12291231
.map(bidRequest -> ortbVersionConversionManager.convertFromAuctionSupportedVersion(
12301232
bidRequest, bidderRequest.getOrtbVersion()))
12311233
.map(bidderRequest::with)
@@ -1240,9 +1242,16 @@ private Future<BidderResponse> requestBids(BidderRequest bidderRequest,
12401242
.map(seatBid -> BidderResponse.of(bidderName, seatBid, responseTime(bidderRequestStartTime)));
12411243
}
12421244

1243-
private BidRequest adjustTmax(BidRequest bidRequest, long startTime, int adjustmentFactor, long currentTime) {
1245+
private BidRequest adjustTmax(BidRequest bidRequest,
1246+
long startTime,
1247+
int adjustmentFactor,
1248+
long currentTime,
1249+
long bidderTmaxDeductionMs) {
1250+
12441251
final long tmax = timeoutResolver.limitToMax(bidRequest.getTmax());
1245-
final long adjustedTmax = timeoutResolver.adjustForBidder(tmax, adjustmentFactor, currentTime - startTime);
1252+
final long adjustedTmax = timeoutResolver.adjustForBidder(
1253+
tmax, adjustmentFactor, currentTime - startTime, bidderTmaxDeductionMs);
1254+
12461255
return tmax != adjustedTmax
12471256
? bidRequest.toBuilder().tmax(adjustedTmax).build()
12481257
: bidRequest;

src/main/java/org/prebid/server/auction/TimeoutResolver.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ public long limitToMax(Long timeout) {
3232
: Math.min(timeout, maxTimeout);
3333
}
3434

35-
public long adjustForBidder(long timeout, int adjustFactor, long spentTime) {
36-
return adjustWithFactor(timeout, adjustFactor / 100.0, spentTime);
35+
public long adjustForBidder(long timeout, int adjustFactor, long spentTime, long bidderTmaxDeductionMs) {
36+
return adjustWithFactor(timeout, adjustFactor / 100.0, spentTime, bidderTmaxDeductionMs);
3737
}
3838

3939
public long adjustForRequest(long timeout, long spentTime) {
40-
return adjustWithFactor(timeout, 1.0, spentTime);
40+
return adjustWithFactor(timeout, 1.0, spentTime, 0L);
4141
}
4242

43-
private long adjustWithFactor(long timeout, double adjustFactor, long spentTime) {
44-
return limitToMin((long) (timeout * adjustFactor) - spentTime - upstreamResponseTime);
43+
private long adjustWithFactor(long timeout, double adjustFactor, long spentTime, long deductionTime) {
44+
return limitToMin((long) (timeout * adjustFactor) - spentTime - deductionTime - upstreamResponseTime);
4545
}
4646

4747
private long limitToMin(long timeout) {

src/main/java/org/prebid/server/bidder/BidderInfo.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public class BidderInfo {
4040

4141
Ortb ortb;
4242

43+
long tmaxDeductionMs;
44+
4345
public static BidderInfo create(boolean enabled,
4446
OrtbVersion ortbVersion,
4547
boolean debugAllowed,
@@ -55,7 +57,8 @@ public static BidderInfo create(boolean enabled,
5557
boolean ccpaEnforced,
5658
boolean modifyingVastXmlAllowed,
5759
CompressionType compressionType,
58-
org.prebid.server.spring.config.bidder.model.Ortb ortb) {
60+
org.prebid.server.spring.config.bidder.model.Ortb ortb,
61+
long tmaxDeductionMs) {
5962

6063
return of(
6164
enabled,
@@ -74,7 +77,8 @@ public static BidderInfo create(boolean enabled,
7477
ccpaEnforced,
7578
modifyingVastXmlAllowed,
7679
compressionType,
77-
Ortb.of(ortb.getMultiFormatSupported()));
80+
Ortb.of(ortb.getMultiFormatSupported()),
81+
tmaxDeductionMs);
7882
}
7983

8084
private static PlatformInfo platformInfo(List<MediaType> mediaTypes) {

src/main/java/org/prebid/server/spring/config/bidder/model/BidderConfigurationProperties.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public class BidderConfigurationProperties {
5151

5252
private Ortb ortb;
5353

54+
private long tmaxDeductionMs;
55+
5456
private final Class<? extends BidderConfigurationProperties> selfClass;
5557

5658
public BidderConfigurationProperties() {

src/main/java/org/prebid/server/spring/config/bidder/util/BidderInfoCreator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public static BidderInfo create(BidderConfigurationProperties configurationPrope
3232
configurationProperties.getPbsEnforcesCcpa(),
3333
configurationProperties.getModifyingVastXmlAllowed(),
3434
configurationProperties.getEndpointCompression(),
35-
configurationProperties.getOrtb());
35+
configurationProperties.getOrtb(),
36+
configurationProperties.getTmaxDeductionMs());
3637
}
3738
}

src/test/groovy/org/prebid/server/functional/tests/TimeoutSpec.groovy

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ class TimeoutSpec extends BaseSpec {
1616

1717
private static final int DEFAULT_TIMEOUT = getRandomTimeout()
1818
private static final int MIN_TIMEOUT = PBSUtils.getRandomNumber(50, 150)
19-
private static final Map PBS_CONFIG = ["auction.biddertmax.max" : MAX_TIMEOUT as String,
20-
"auction.biddertmax.min" : MIN_TIMEOUT as String]
19+
private static final Long MAX_AUCTION_BIDDER_TIMEOUT = 3000
20+
private static final Long MIN_AUCTION_BIDDER_TIMEOUT = 1000
21+
private static final Map PBS_CONFIG = ["auction.biddertmax.max": MAX_AUCTION_BIDDER_TIMEOUT as String,
22+
"auction.biddertmax.min": MIN_AUCTION_BIDDER_TIMEOUT as String]
2123

2224
@Shared
2325
PrebidServerService prebidServerService = pbsServiceFactory.getService(PBS_CONFIG)
@@ -136,9 +138,10 @@ class TimeoutSpec extends BaseSpec {
136138

137139
and: "Pbs config with default request"
138140
def pbsContainer = new PrebidServerContainer(
139-
["default-request.file.path" : APP_WORKDIR + defaultRequest.fileName,
140-
"auction.biddertmax.max" : MAX_TIMEOUT as String]).tap {
141-
withCopyFileToContainer(MountableFile.forHostPath(defaultRequest), APP_WORKDIR) }
141+
["default-request.file.path": APP_WORKDIR + defaultRequest.fileName,
142+
"auction.biddertmax.max" : MAX_TIMEOUT as String]).tap {
143+
withCopyFileToContainer(MountableFile.forHostPath(defaultRequest), APP_WORKDIR)
144+
}
142145
pbsContainer.start()
143146
def pbsService = new PrebidServerService(pbsContainer)
144147

@@ -284,8 +287,9 @@ class TimeoutSpec extends BaseSpec {
284287
def "PBS should choose min timeout form config for bidder request when in request value lowest that in auction.biddertmax.min"() {
285288
given: "PBS config with percent"
286289
def minBidderTmax = PBSUtils.getRandomNumber(MIN_TIMEOUT, MAX_TIMEOUT)
287-
def prebidServerService = pbsServiceFactory.getService(["auction.biddertmax.min" : minBidderTmax as String,
288-
"auction.biddertmax.max" : MAX_TIMEOUT as String])
290+
def prebidServerService = pbsServiceFactory.getService(
291+
["auction.biddertmax.min": minBidderTmax as String,
292+
"auction.biddertmax.max": MAX_TIMEOUT as String])
289293

290294
and: "Default basic BidRequest"
291295
def timeout = PBSUtils.getRandomNumber(0, minBidderTmax)
@@ -307,11 +311,14 @@ class TimeoutSpec extends BaseSpec {
307311
def "PBS should change timeout for bidder due to percent in auction.biddertmax.percent"() {
308312
given: "PBS config with percent"
309313
def percent = PBSUtils.getRandomNumber(2, 98)
310-
def prebidServerService = pbsServiceFactory.getService(["auction.biddertmax.percent": percent as String]
311-
+ PBS_CONFIG)
314+
def pbsConfig = ["auction.biddertmax.percent": percent as String,
315+
"auction.biddertmax.max" : MAX_TIMEOUT as String,
316+
"auction.biddertmax.min" : MIN_TIMEOUT as String]
317+
def prebidServerService = pbsServiceFactory.getService(
318+
pbsConfig)
312319

313320
and: "Default basic BidRequest with generic bidder"
314-
def timeout = getRandomTimeout()
321+
def timeout = randomTimeout
315322
def bidRequest = BidRequest.defaultBidRequest.tap {
316323
tmax = timeout
317324
}
@@ -321,17 +328,97 @@ class TimeoutSpec extends BaseSpec {
321328

322329
then: "Bidder request should contain percent of request value"
323330
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
324-
assert isInternalProcessingTime(bidderRequest.tmax, getPercentOfValue(percent,timeout))
331+
assert isInternalProcessingTime(bidderRequest.tmax, getPercentOfValue(percent, timeout))
325332

326333
and: "PBS response should contain tmax from request"
327334
assert bidResponse?.ext?.tmaxrequest == timeout as Long
335+
336+
cleanup: "Stop and remove pbs container"
337+
pbsServiceFactory.removeContainer(pbsConfig)
338+
}
339+
340+
def "PBS should apply auction.biddertmax.max timeout when adapters.generic.tmax-deduction-ms exceeds valid top range"() {
341+
given: "PBS config with adapters.generic.tmax-deduction-ms"
342+
def pbsConfig = PBS_CONFIG + ["adapters.generic.tmax-deduction-ms": PBSUtils.getRandomNumber(MAX_AUCTION_BIDDER_TIMEOUT as int) as String]
343+
def prebidServerService = pbsServiceFactory.getService(pbsConfig)
344+
345+
and: "Default basic BidRequest with generic bidder"
346+
def bidRequest = BidRequest.defaultBidRequest.tap {
347+
tmax = randomTimeout
348+
}
349+
350+
when: "PBS processes auction request"
351+
def bidResponse = prebidServerService.sendAuctionRequest(bidRequest)
352+
353+
then: "Bidder request should contain min"
354+
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
355+
assert bidderRequest.tmax == MIN_AUCTION_BIDDER_TIMEOUT
356+
357+
and: "PBS response should contain tmax"
358+
assert bidResponse?.ext?.tmaxrequest == MAX_AUCTION_BIDDER_TIMEOUT
359+
360+
cleanup: "Stop and remove pbs container"
361+
pbsServiceFactory.removeContainer(pbsConfig)
362+
}
363+
364+
def "PBS should resolve timeout as usual when adapters.generic.tmax-deduction-ms specifies zero"() {
365+
given: "PBS config with adapters.generic.tmax-deduction-ms"
366+
def pbsConfig = ["adapters.generic.tmax-deduction-ms": "0"] + PBS_CONFIG
367+
def prebidServerService = pbsServiceFactory.getService(pbsConfig)
368+
369+
and: "Default basic BidRequest with generic bidder"
370+
def timeout = PBSUtils.getRandomNumber(
371+
MIN_AUCTION_BIDDER_TIMEOUT as int,
372+
MAX_AUCTION_BIDDER_TIMEOUT as int)
373+
def bidRequest = BidRequest.defaultBidRequest.tap {
374+
tmax = timeout
375+
}
376+
377+
when: "PBS processes auction request"
378+
def bidResponse = prebidServerService.sendAuctionRequest(bidRequest)
379+
380+
then: "Bidder request should contain right value in tmax"
381+
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
382+
assert isInternalProcessingTime(bidderRequest.tmax, timeout)
383+
384+
and: "PBS response should contain tmax"
385+
assert bidResponse?.ext?.tmaxrequest == timeout as Long
386+
387+
cleanup: "Stop and remove pbs container"
388+
pbsServiceFactory.removeContainer(pbsConfig)
389+
}
390+
391+
def "PBS should properly resolve tmax deduction ms when adapters.generic.tmax-deduction-ms specified"() {
392+
given: "PBS config with adapters.generic.tmax-deduction-ms"
393+
def genericDeductionMs = PBSUtils.getRandomNumber(100, 300)
394+
def randomTimeout = PBSUtils.getRandomNumber(MIN_AUCTION_BIDDER_TIMEOUT + genericDeductionMs as int, MAX_AUCTION_BIDDER_TIMEOUT as int)
395+
def pbsConfig = PBS_CONFIG + ["adapters.generic.tmax-deduction-ms": genericDeductionMs as String]
396+
def prebidServerService = pbsServiceFactory.getService(pbsConfig)
397+
398+
and: "Default basic BidRequest with generic bidder"
399+
def bidRequest = BidRequest.defaultBidRequest.tap {
400+
tmax = randomTimeout
401+
}
402+
403+
when: "PBS processes auction request"
404+
def bidResponse = prebidServerService.sendAuctionRequest(bidRequest)
405+
406+
then: "Bidder request should contain right value in tmax"
407+
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
408+
assert isInternalProcessingTime(bidderRequest.tmax, randomTimeout)
409+
410+
and: "PBS response should contain tmax"
411+
assert bidResponse?.ext?.tmaxrequest == randomTimeout as Long
412+
413+
cleanup: "Stop and remove pbs container"
414+
pbsServiceFactory.removeContainer(pbsConfig)
328415
}
329416

330417
private static long getPercentOfValue(int percent, int value) {
331418
(percent * value) / 100.0 as Long
332419
}
333420

334-
private static boolean isInternalProcessingTime(long bidderRequestTimeout, long requestTimeout){
421+
private static boolean isInternalProcessingTime(long bidderRequestTimeout, long requestTimeout) {
335422
0 < requestTimeout - bidderRequestTimeout
336423
}
337424
}

src/test/java/org/prebid/server/auction/ExchangeServiceTest.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,8 @@ public void setUp() {
310310
false,
311311
false,
312312
CompressionType.NONE,
313-
Ortb.of(false)));
313+
Ortb.of(false),
314+
0L));
314315

315316
given(privacyEnforcementService.mask(any(), argThat(MapUtils::isNotEmpty), any()))
316317
.willAnswer(inv ->
@@ -381,7 +382,7 @@ public void setUp() {
381382
given(criteriaLogManager.traceResponse(any(), any(), any(), anyBoolean()))
382383
.willAnswer(inv -> inv.getArgument(1));
383384

384-
given(timeoutResolver.adjustForBidder(anyLong(), anyInt(), anyLong()))
385+
given(timeoutResolver.adjustForBidder(anyLong(), anyInt(), anyLong(), anyLong()))
385386
.willAnswer(invocation -> invocation.getArgument(0));
386387

387388
given(timeoutResolver.adjustForRequest(anyLong(), anyLong()))
@@ -3774,7 +3775,9 @@ public void shouldResponseWithEmptySeatBidIfBidderNotSupportRequestCurrency() {
37743775
false,
37753776
false,
37763777
CompressionType.NONE,
3777-
Ortb.of(false)));
3778+
Ortb.of(false),
3779+
0L));
3780+
37783781
given(bidResponseCreator.create(
37793782
argThat(argument -> argument.getAuctionParticipations().getFirst()
37803783
.getBidderResponse()
@@ -3837,17 +3840,35 @@ public void shouldConvertBidRequestOpenRTBVersionToConfiguredByBidder() {
38373840
@Test
38383841
public void shouldPassAdjustedTimeoutToAdapterAndToBidResponseCreator() {
38393842
// given
3840-
given(timeoutResolver.adjustForBidder(anyLong(), eq(90), anyLong()))
3841-
.willReturn(400L);
3842-
given(timeoutResolver.adjustForRequest(anyLong(), anyLong()))
3843-
.willReturn(450L);
3843+
given(bidderCatalog.bidderInfoByName(anyString())).willReturn(BidderInfo.create(
3844+
true,
3845+
null,
3846+
false,
3847+
null,
3848+
null,
3849+
null,
3850+
null,
3851+
null,
3852+
null,
3853+
null,
3854+
0,
3855+
null,
3856+
false,
3857+
false,
3858+
CompressionType.NONE,
3859+
Ortb.of(false),
3860+
100L));
3861+
3862+
given(timeoutResolver.adjustForBidder(anyLong(), eq(90), eq(200L), eq(100L))).willReturn(400L);
3863+
given(timeoutResolver.adjustForRequest(anyLong(), eq(200L))).willReturn(450L);
38443864

38453865
final BidRequest bidRequest = givenBidRequest(
38463866
givenSingleImp(singletonMap("bidderName", 1)),
38473867
request -> request.source(Source.builder().tid("uniqTid").build()));
38483868

38493869
// when
3850-
target.holdAuction(givenRequestContext(bidRequest));
3870+
target.holdAuction(givenRequestContext(bidRequest).toBuilder()
3871+
.timeoutContext(TimeoutContext.of(clock.millis() - 200L, timeout, 90)).build());
38513872

38523873
// then
38533874
final ArgumentCaptor<BidderRequest> bidderRequestCaptor = ArgumentCaptor.forClass(BidderRequest.class);

src/test/java/org/prebid/server/auction/TimeoutResolverTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ public void limitToMaxShouldReturnMaxTimeout() {
5353

5454
@Test
5555
public void adjustForBidderShouldReturnExpectedResult() {
56-
assertThat(timeoutResolver.adjustForBidder(200L, 70, 10L)).isEqualTo(120L);
56+
assertThat(timeoutResolver.adjustForBidder(300L, 70, 10L, 50L)).isEqualTo(140L);
5757
}
5858

5959
@Test
6060
public void adjustForBidderShouldReturnMinTimeout() {
61-
assertThat(timeoutResolver.adjustForBidder(200L, 50, 10L)).isEqualTo(MIN_TIMEOUT);
61+
assertThat(timeoutResolver.adjustForBidder(200L, 50, 10L, 100L)).isEqualTo(MIN_TIMEOUT);
6262
}
6363

6464
@Test

0 commit comments

Comments
 (0)