From eee8e530b19cd9df75f72ecc2cf65930f7d4643b Mon Sep 17 00:00:00 2001 From: antonbabak Date: Fri, 18 Jul 2025 14:54:56 +0200 Subject: [PATCH 01/10] Add Get /vtrack --- .../prebid/server/cache/CoreCacheService.java | 66 ++++++++-- .../proto/response/CacheErrorResponse.java | 19 +++ .../server/handler/GetVtrackHandler.java | 105 +++++++++++++++ ...ackHandler.java => PostVtrackHandler.java} | 20 +-- .../ApplicationServerConfiguration.java | 15 ++- .../server/cache/CoreCacheServiceTest.java | 109 ++++++++++++++++ .../server/handler/GetVtrackHandlerTest.java | 123 ++++++++++++++++++ ...erTest.java => PostVtrackHandlerTest.java} | 14 +- .../org/prebid/server/it/ApplicationTest.java | 21 ++- .../org/prebid/server/it/IntegrationTest.java | 8 ++ .../server/it/vtrack/test-vtrack-response.xml | 12 ++ 11 files changed, 482 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/prebid/server/cache/proto/response/CacheErrorResponse.java create mode 100644 src/main/java/org/prebid/server/handler/GetVtrackHandler.java rename src/main/java/org/prebid/server/handler/{VtrackHandler.java => PostVtrackHandler.java} (94%) create mode 100644 src/test/java/org/prebid/server/handler/GetVtrackHandlerTest.java rename src/test/java/org/prebid/server/handler/{VtrackHandlerTest.java => PostVtrackHandlerTest.java} (98%) create mode 100644 src/test/resources/org/prebid/server/it/vtrack/test-vtrack-response.xml diff --git a/src/main/java/org/prebid/server/cache/CoreCacheService.java b/src/main/java/org/prebid/server/cache/CoreCacheService.java index 863c25ee38b..ea1ea41fe29 100644 --- a/src/main/java/org/prebid/server/cache/CoreCacheService.java +++ b/src/main/java/org/prebid/server/cache/CoreCacheService.java @@ -9,6 +9,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.utils.URIBuilder; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidInfo; import org.prebid.server.auction.model.CachedDebugLog; @@ -22,6 +23,7 @@ import org.prebid.server.cache.model.DebugHttpCall; import org.prebid.server.cache.proto.request.bid.BidCacheRequest; import org.prebid.server.cache.proto.request.bid.BidPutObject; +import org.prebid.server.cache.proto.response.CacheErrorResponse; import org.prebid.server.cache.proto.response.bid.BidCacheResponse; import org.prebid.server.cache.proto.response.bid.CacheObject; import org.prebid.server.cache.utils.CacheServiceUtil; @@ -45,6 +47,7 @@ import org.prebid.server.vertx.httpclient.HttpClient; import org.prebid.server.vertx.httpclient.model.HttpClientResponse; +import java.net.URISyntaxException; import java.net.URL; import java.time.Clock; import java.util.ArrayList; @@ -66,6 +69,8 @@ public class CoreCacheService { private static final String BID_WURL_ATTRIBUTE = "wurl"; private static final String TRACE_INFO_SEPARATOR = "-"; private static final int MAX_DATACENTER_REGION_LENGTH = 4; + private static final String UUID_QUERY_PARAMETER = "uuid"; + private static final String CH_QUERY_PARAMETER = "ch"; private final HttpClient httpClient; private final URL externalEndpointUrl; @@ -191,15 +196,6 @@ private Future makeRequest(BidCacheRequest bidCacheRequest, .recover(exception -> failResponse(exception, accountId, startTime)); } - private Future failResponse(Throwable exception, String accountId, long startTime) { - metrics.updateCacheRequestFailedTime(accountId, clock.millis() - startTime); - - logger.warn("Error occurred while interacting with cache service: {}", exception.getMessage()); - logger.debug("Error occurred while interacting with cache service", exception); - - return Future.failedFuture(exception); - } - public Future cachePutObjects(List bidPutObjects, Boolean isEventsEnabled, Set biddersAllowingVastUpdate, @@ -627,4 +623,56 @@ private static String normalizeDatacenterRegion(String datacenterRegion) { ? trimmedDatacenterRegion.substring(0, MAX_DATACENTER_REGION_LENGTH) : trimmedDatacenterRegion; } + + public Future getCachedObject(String key, String ch, Timeout timeout) { + final long remainingTimeout = timeout.remaining(); + if (remainingTimeout <= 0) { + return Future.failedFuture(new TimeoutException("Timeout has been exceeded")); + } + + final URL endpointUrl = ObjectUtils.firstNonNull(internalEndpointUrl, externalEndpointUrl); + final String url; + try { + final URIBuilder uriBuilder = new URIBuilder(endpointUrl.toString()); + uriBuilder.addParameter(UUID_QUERY_PARAMETER, key); + if (StringUtils.isNotBlank(ch)) { + uriBuilder.addParameter(CH_QUERY_PARAMETER, ch); + } + url = uriBuilder.build().toString(); + } catch (URISyntaxException e) { + return Future.failedFuture(new IllegalArgumentException("Configured cache url is malformed", e)); + } + + return httpClient.get(url, cacheHeaders, remainingTimeout) + .map(this::handleResponse) + .recover(CoreCacheService::failResponse); + } + + private HttpClientResponse handleResponse(HttpClientResponse response) { + final int statusCode = response.getStatusCode(); + final String body = response.getBody(); + + if (statusCode == 200) { + return response; + } + + try { + final CacheErrorResponse errorResponse = mapper.decodeValue(body, CacheErrorResponse.class); + return HttpClientResponse.of(statusCode, response.getHeaders(), errorResponse.getMessage()); + } catch (DecodeException e) { + throw new PreBidException("Cannot parse response: " + body, e); + } + } + + private Future failResponse(Throwable exception, String accountId, long startTime) { + metrics.updateCacheRequestFailedTime(accountId, clock.millis() - startTime); + return failResponse(exception); + } + + private static Future failResponse(Throwable exception) { + logger.warn("Error occurred while interacting with cache service: {}", exception.getMessage()); + logger.debug("Error occurred while interacting with cache service", exception); + + return Future.failedFuture(exception); + } } diff --git a/src/main/java/org/prebid/server/cache/proto/response/CacheErrorResponse.java b/src/main/java/org/prebid/server/cache/proto/response/CacheErrorResponse.java new file mode 100644 index 00000000000..cf535ac1181 --- /dev/null +++ b/src/main/java/org/prebid/server/cache/proto/response/CacheErrorResponse.java @@ -0,0 +1,19 @@ +package org.prebid.server.cache.proto.response; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class CacheErrorResponse { + + String error; + + Integer status; + + String path; + + String message; + + Long timestamp; +} diff --git a/src/main/java/org/prebid/server/handler/GetVtrackHandler.java b/src/main/java/org/prebid/server/handler/GetVtrackHandler.java new file mode 100644 index 00000000000..deaa9a5b2b5 --- /dev/null +++ b/src/main/java/org/prebid/server/handler/GetVtrackHandler.java @@ -0,0 +1,105 @@ +package org.prebid.server.handler; + +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.vertx.core.AsyncResult; +import io.vertx.core.MultiMap; +import io.vertx.core.http.HttpMethod; +import io.vertx.ext.web.RoutingContext; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.cache.CoreCacheService; +import org.prebid.server.execution.timeout.Timeout; +import org.prebid.server.execution.timeout.TimeoutFactory; +import org.prebid.server.log.Logger; +import org.prebid.server.log.LoggerFactory; +import org.prebid.server.model.Endpoint; +import org.prebid.server.util.HttpUtil; +import org.prebid.server.vertx.httpclient.model.HttpClientResponse; +import org.prebid.server.vertx.verticles.server.HttpEndpoint; +import org.prebid.server.vertx.verticles.server.application.ApplicationResource; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class GetVtrackHandler implements ApplicationResource { + + private static final Logger logger = LoggerFactory.getLogger(GetVtrackHandler.class); + + private static final String UUID_PARAMETER = "uuid"; + private static final String CH_PARAMETER = "ch"; + + private final long defaultTimeout; + private final CoreCacheService coreCacheService; + private final TimeoutFactory timeoutFactory; + + public GetVtrackHandler(long defaultTimeout, CoreCacheService coreCacheService, TimeoutFactory timeoutFactory) { + this.defaultTimeout = defaultTimeout; + this.coreCacheService = Objects.requireNonNull(coreCacheService); + this.timeoutFactory = Objects.requireNonNull(timeoutFactory); + } + + @Override + public List endpoints() { + return Collections.singletonList(HttpEndpoint.of(HttpMethod.GET, Endpoint.vtrack.value())); + } + + @Override + public void handle(RoutingContext routingContext) { + final String uuid = routingContext.request().getParam(UUID_PARAMETER); + final String ch = routingContext.request().getParam(CH_PARAMETER); + if (StringUtils.isBlank(uuid)) { + respondWith( + routingContext, + HttpResponseStatus.BAD_REQUEST, + "'%s' is a required query parameter and can't be empty".formatted(UUID_PARAMETER)); + return; + } + + final Timeout timeout = timeoutFactory.create(defaultTimeout); + + coreCacheService.getCachedObject(uuid, ch, timeout) + .onComplete(asyncCache -> handleCacheResult(asyncCache, routingContext)); + } + + private static void respondWithServerError(RoutingContext routingContext, Throwable exception) { + logger.error("Error occurred while sending request to cache", exception); + respondWith(routingContext, HttpResponseStatus.INTERNAL_SERVER_ERROR, + "%s: %s".formatted("Error occurred while sending request to cache", exception.getMessage())); + } + + private static void respondWith(RoutingContext routingContext, + HttpResponseStatus status, + MultiMap headers, + String body) { + + HttpUtil.executeSafely(routingContext, Endpoint.vtrack, + response -> { + headers.forEach(response::putHeader); + response.setStatusCode(status.code()) + .end(body); + }); + } + + private static void respondWith(RoutingContext routingContext, HttpResponseStatus status, String body) { + HttpUtil.executeSafely(routingContext, Endpoint.vtrack, + response -> response + .putHeader(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON) + .setStatusCode(status.code()) + .end(body)); + } + + private void handleCacheResult(AsyncResult async, RoutingContext routingContext) { + if (async.failed()) { + respondWithServerError(routingContext, async.cause()); + } else { + final HttpClientResponse response = async.result(); + final HttpResponseStatus status = HttpResponseStatus.valueOf(response.getStatusCode()); + if (status == HttpResponseStatus.OK) { + respondWith(routingContext, status, response.getHeaders(), response.getBody()); + } else { + respondWith(routingContext, status, response.getBody()); + } + } + } +} diff --git a/src/main/java/org/prebid/server/handler/VtrackHandler.java b/src/main/java/org/prebid/server/handler/PostVtrackHandler.java similarity index 94% rename from src/main/java/org/prebid/server/handler/VtrackHandler.java rename to src/main/java/org/prebid/server/handler/PostVtrackHandler.java index f8b67b50d8c..194d8a4f3fb 100644 --- a/src/main/java/org/prebid/server/handler/VtrackHandler.java +++ b/src/main/java/org/prebid/server/handler/PostVtrackHandler.java @@ -39,9 +39,9 @@ import java.util.Set; import java.util.stream.Collectors; -public class VtrackHandler implements ApplicationResource { +public class PostVtrackHandler implements ApplicationResource { - private static final Logger logger = LoggerFactory.getLogger(VtrackHandler.class); + private static final Logger logger = LoggerFactory.getLogger(PostVtrackHandler.class); private static final String ACCOUNT_PARAMETER = "a"; private static final String INTEGRATION_PARAMETER = "int"; @@ -56,14 +56,14 @@ public class VtrackHandler implements ApplicationResource { private final TimeoutFactory timeoutFactory; private final JacksonMapper mapper; - public VtrackHandler(long defaultTimeout, - boolean allowUnknownBidder, - boolean modifyVastForUnknownBidder, - ApplicationSettings applicationSettings, - BidderCatalog bidderCatalog, - CoreCacheService coreCacheService, - TimeoutFactory timeoutFactory, - JacksonMapper mapper) { + public PostVtrackHandler(long defaultTimeout, + boolean allowUnknownBidder, + boolean modifyVastForUnknownBidder, + ApplicationSettings applicationSettings, + BidderCatalog bidderCatalog, + CoreCacheService coreCacheService, + TimeoutFactory timeoutFactory, + JacksonMapper mapper) { this.defaultTimeout = defaultTimeout; this.allowUnknownBidder = allowUnknownBidder; diff --git a/src/main/java/org/prebid/server/spring/config/server/application/ApplicationServerConfiguration.java b/src/main/java/org/prebid/server/spring/config/server/application/ApplicationServerConfiguration.java index 88f1ef32f92..c6ae167ae94 100644 --- a/src/main/java/org/prebid/server/spring/config/server/application/ApplicationServerConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/server/application/ApplicationServerConfiguration.java @@ -34,13 +34,14 @@ import org.prebid.server.handler.BidderParamHandler; import org.prebid.server.handler.CookieSyncHandler; import org.prebid.server.handler.ExceptionHandler; +import org.prebid.server.handler.GetVtrackHandler; import org.prebid.server.handler.GetuidsHandler; import org.prebid.server.handler.NoCacheHandler; import org.prebid.server.handler.NotificationEventHandler; import org.prebid.server.handler.OptoutHandler; import org.prebid.server.handler.SetuidHandler; import org.prebid.server.handler.StatusHandler; -import org.prebid.server.handler.VtrackHandler; +import org.prebid.server.handler.PostVtrackHandler; import org.prebid.server.handler.info.BidderDetailsHandler; import org.prebid.server.handler.info.BiddersHandler; import org.prebid.server.handler.info.filters.BaseOnlyBidderInfoFilterStrategy; @@ -370,7 +371,7 @@ GetuidsHandler getuidsHandler(UidsCookieService uidsCookieService, JacksonMapper } @Bean - VtrackHandler vtrackHandler( + PostVtrackHandler postVtrackHandler( @Value("${vtrack.default-timeout-ms}") int defaultTimeoutMs, @Value("${vtrack.allow-unknown-bidder}") boolean allowUnknownBidder, @Value("${vtrack.modify-vast-for-unknown-bidder}") boolean modifyVastForUnknownBidder, @@ -380,7 +381,7 @@ VtrackHandler vtrackHandler( TimeoutFactory timeoutFactory, JacksonMapper mapper) { - return new VtrackHandler( + return new PostVtrackHandler( defaultTimeoutMs, allowUnknownBidder, modifyVastForUnknownBidder, @@ -391,6 +392,14 @@ VtrackHandler vtrackHandler( mapper); } + @Bean + GetVtrackHandler getVtrackHandler(@Value("${vtrack.default-timeout-ms}") int defaultTimeoutMs, + CoreCacheService coreCacheService, + TimeoutFactory timeoutFactory) { + + return new GetVtrackHandler(defaultTimeoutMs, coreCacheService, timeoutFactory); + } + @Bean OptoutHandler optoutHandler( @Value("${external-url}") String externalUrl, diff --git a/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java b/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java index 6ce3836a695..0697e0c8b51 100644 --- a/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java +++ b/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java @@ -27,6 +27,7 @@ import org.prebid.server.cache.model.DebugHttpCall; import org.prebid.server.cache.proto.request.bid.BidCacheRequest; import org.prebid.server.cache.proto.request.bid.BidPutObject; +import org.prebid.server.cache.proto.response.CacheErrorResponse; import org.prebid.server.cache.proto.response.bid.BidCacheResponse; import org.prebid.server.cache.proto.response.bid.CacheObject; import org.prebid.server.events.EventsContext; @@ -1356,6 +1357,114 @@ public void cachePutObjectsShouldNotEmitEmptyTtlMetrics() { verify(metrics, never()).updateCacheCreativeTtl(any(), any(), any()); } + @Test + public void getCachedObjectShouldAddUuidAndChQueryParamsBeforeSendingWhenChIsPresent() { + // given + final HttpClientResponse response = HttpClientResponse.of( + 200, + MultiMap.caseInsensitiveMultiMap().add("Header", "Value"), + "body"); + + given(httpClient.get(eq("http://cache-service/cache?uuid=key&ch=ch"), any(), anyLong())) + .willReturn(Future.succeededFuture(response)); + + // when + final Future result = target.getCachedObject("key", "ch", timeout); + + // then + assertThat(result.result()).isEqualTo(response); + } + + @Test + public void getCachedObjectShouldAddUuidQueryParamsBeforeSendingWhenChIsAbsent() { + // given + final HttpClientResponse response = HttpClientResponse.of( + 200, + MultiMap.caseInsensitiveMultiMap().add("Header", "Value"), + "body"); + + given(httpClient.get(eq("http://cache-service/cache?uuid=key"), any(), anyLong())) + .willReturn(Future.succeededFuture(response)); + + // when + final Future result = target.getCachedObject("key", null, timeout); + + // then + assertThat(result.result()).isEqualTo(response); + } + + @Test + public void getCachedObjectShouldAddUuidQueryParamsToInternalBeforeSendingWhenChIsAbsent() + throws MalformedURLException { + + // given + target = new CoreCacheService( + httpClient, + new URL("http://cache-service/cache"), + new URL("http://internal-cache-service/cache"), + "http://cache-service-host/cache?uuid=", + 100L, + "ApiKey", + false, + true, + "apacific", + vastModifier, + eventsService, + metrics, + clock, + idGenerator, + jacksonMapper); + + final HttpClientResponse response = HttpClientResponse.of( + 200, + MultiMap.caseInsensitiveMultiMap().add("Header", "Value"), + "body"); + + given(httpClient.get(eq("http://internal-cache-service/cache?uuid=key"), any(), anyLong())) + .willReturn(Future.succeededFuture(response)); + + // when + final Future result = target.getCachedObject("key", null, timeout); + + // then + assertThat(result.result()).isEqualTo(response); + } + + @Test + public void getCachedObjectShouldHandleErrorResponse() { + // given + final HttpClientResponse response = HttpClientResponse.of( + 404, + null, + jacksonMapper.encodeToString(CacheErrorResponse.builder().message("Resource not found").build())); + + given(httpClient.get(eq("http://cache-service/cache?uuid=key&ch=ch"), any(), anyLong())) + .willReturn(Future.succeededFuture(response)); + + // when + final Future result = target.getCachedObject("key", "ch", timeout); + + // then + assertThat(result.result()).isEqualTo(HttpClientResponse.of(404, null, "Resource not found")); + } + + @Test + public void getCachedObjectShouldFailWhenErrorResponseCanNotBeParsed() { + // given + final HttpClientResponse response = HttpClientResponse.of(404, null, "Resource not found"); + + given(httpClient.get(eq("http://cache-service/cache?uuid=key&ch=ch"), any(), anyLong())) + .willReturn(Future.succeededFuture(response)); + + // when + final Future result = target.getCachedObject("key", "ch", timeout); + + // then + assertThat(result.failed()).isTrue(); + assertThat(result.cause()).hasMessage("Cannot parse response: Resource not found"); + assertThat(result.cause()).isInstanceOf(PreBidException.class); + } + private AuctionContext givenAuctionContext(UnaryOperator accountCustomizer, UnaryOperator bidRequestCustomizer) { diff --git a/src/test/java/org/prebid/server/handler/GetVtrackHandlerTest.java b/src/test/java/org/prebid/server/handler/GetVtrackHandlerTest.java new file mode 100644 index 00000000000..f4d3bca3d3c --- /dev/null +++ b/src/test/java/org/prebid/server/handler/GetVtrackHandlerTest.java @@ -0,0 +1,123 @@ +package org.prebid.server.handler; + +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.util.AsciiString; +import io.vertx.core.Future; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.VertxTest; +import org.prebid.server.cache.CoreCacheService; +import org.prebid.server.execution.timeout.TimeoutFactory; +import org.prebid.server.util.HttpUtil; +import org.prebid.server.vertx.httpclient.model.HttpClientResponse; + +import static io.vertx.core.MultiMap.caseInsensitiveMultiMap; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +@ExtendWith(MockitoExtension.class) +public class GetVtrackHandlerTest extends VertxTest { + + @Mock + private CoreCacheService coreCacheService; + @Mock + private TimeoutFactory timeoutFactory; + + private GetVtrackHandler target; + + @Mock(strictness = LENIENT) + private RoutingContext routingContext; + @Mock(strictness = LENIENT) + private HttpServerRequest httpRequest; + @Mock(strictness = LENIENT) + private HttpServerResponse httpResponse; + + @BeforeEach + public void setUp() { + given(routingContext.request()).willReturn(httpRequest); + given(routingContext.response()).willReturn(httpResponse); + given(httpResponse.putHeader(any(CharSequence.class), any(AsciiString.class))).willReturn(httpResponse); + given(httpResponse.putHeader(anyString(), anyString())).willReturn(httpResponse); + + given(httpRequest.getParam("uuid")).willReturn("key"); + given(httpRequest.getParam("ch")).willReturn("test.com"); + + given(httpResponse.setStatusCode(anyInt())).willReturn(httpResponse); + + target = new GetVtrackHandler(2000, coreCacheService, timeoutFactory); + } + + @Test + public void shouldRespondWithBadRequestWhenAccountParameterIsMissing() { + // given + given(httpRequest.getParam("uuid")).willReturn(null); + + // when + target.handle(routingContext); + + // then + verifyNoInteractions(coreCacheService); + + verify(httpResponse).setStatusCode(400); + verify(httpResponse).end("'uuid' is a required query parameter and can't be empty"); + } + + @Test + public void shouldRespondWithInternalServerErrorWhenCacheServiceReturnFailure() { + // given + given(coreCacheService.getCachedObject(eq("key"), eq("test.com"), any())) + .willReturn(Future.failedFuture("error")); + + // when + target.handle(routingContext); + + // then + verify(httpResponse).setStatusCode(500); + verify(httpResponse).putHeader(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); + verify(httpResponse).end("Error occurred while sending request to cache: error"); + } + + @Test + public void shouldRespondWithBodyAndHeadersReturnedFromCacheWhenStatusCodeIsOk() { + // given + given(coreCacheService.getCachedObject(any(), any(), any())).willReturn(Future.succeededFuture( + HttpClientResponse.of(200, caseInsensitiveMultiMap().add("Header", "Value"), "body"))); + + // when + target.handle(routingContext); + + // then + verify(coreCacheService).getCachedObject(eq("key"), eq("test.com"), any()); + verify(httpResponse).setStatusCode(200); + verify(httpResponse).putHeader("Header", "Value"); + verify(httpResponse).end("body"); + } + + @Test + public void shouldRespondWithBodyAndDefaultHeadersReturnedFromCacheWhenStatusCodeIsNotOk() { + // given + given(coreCacheService.getCachedObject(any(), any(), any())).willReturn(Future.succeededFuture( + HttpClientResponse.of(404, caseInsensitiveMultiMap().add("Header", "Value"), "reason"))); + + // when + target.handle(routingContext); + + // then + verify(coreCacheService).getCachedObject(eq("key"), eq("test.com"), any()); + verify(httpResponse).setStatusCode(404); + verify(httpResponse).putHeader(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); + verify(httpResponse).end("reason"); + } +} diff --git a/src/test/java/org/prebid/server/handler/VtrackHandlerTest.java b/src/test/java/org/prebid/server/handler/PostVtrackHandlerTest.java similarity index 98% rename from src/test/java/org/prebid/server/handler/VtrackHandlerTest.java rename to src/test/java/org/prebid/server/handler/PostVtrackHandlerTest.java index a48af60aabf..98011db6ecf 100644 --- a/src/test/java/org/prebid/server/handler/VtrackHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/PostVtrackHandlerTest.java @@ -51,7 +51,7 @@ import static org.mockito.Mockito.verifyNoInteractions; @ExtendWith(MockitoExtension.class) -public class VtrackHandlerTest extends VertxTest { +public class PostVtrackHandlerTest extends VertxTest { @Mock private ApplicationSettings applicationSettings; @@ -62,7 +62,7 @@ public class VtrackHandlerTest extends VertxTest { @Mock private TimeoutFactory timeoutFactory; - private VtrackHandler handler; + private PostVtrackHandler handler; @Mock(strictness = LENIENT) private RoutingContext routingContext; @Mock(strictness = LENIENT) @@ -84,7 +84,7 @@ public void setUp() { given(httpResponse.setStatusCode(anyInt())).willReturn(httpResponse); - handler = new VtrackHandler( + handler = new PostVtrackHandler( 2000, true, true, applicationSettings, bidderCatalog, coreCacheService, timeoutFactory, jacksonMapper); } @@ -314,7 +314,7 @@ public void shouldSendToCacheNullInAccountEnabledAndValidBiddersWhenAccountEvent public void shouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastNotAllowed() throws JsonProcessingException { // given - handler = new VtrackHandler( + handler = new PostVtrackHandler( 2000, false, true, applicationSettings, bidderCatalog, coreCacheService, timeoutFactory, jacksonMapper); final List bidPutObjects = asList( @@ -359,7 +359,7 @@ public void shouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastNotAll @Test public void shouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastAllowed() throws JsonProcessingException { // given - handler = new VtrackHandler( + handler = new PostVtrackHandler( 2000, false, false, applicationSettings, bidderCatalog, coreCacheService, timeoutFactory, jacksonMapper); @@ -447,7 +447,7 @@ public void shouldSendToCacheExpectedPutsWhenModifyVastForUnknownBidderAndAllowU public void shouldSendToCacheWithEmptyBiddersAllowingVastUpdatePutsWhenAllowUnknownBidderIsFalse() throws JsonProcessingException { // given - handler = new VtrackHandler( + handler = new PostVtrackHandler( 2000, false, true, applicationSettings, bidderCatalog, coreCacheService, timeoutFactory, jacksonMapper); final List bidPutObjects = asList( @@ -486,7 +486,7 @@ public void shouldSendToCacheWithEmptyBiddersAllowingVastUpdatePutsWhenAllowUnkn public void shouldSendToCacheWithEmptyBiddersAllowingVastUpdatePutsWhenModifyVastForUnknownBidderIsFalse() throws JsonProcessingException { // given - handler = new VtrackHandler( + handler = new PostVtrackHandler( 2000, true, false, applicationSettings, bidderCatalog, coreCacheService, timeoutFactory, jacksonMapper); final List bidPutObjects = asList( diff --git a/src/test/java/org/prebid/server/it/ApplicationTest.java b/src/test/java/org/prebid/server/it/ApplicationTest.java index ca4414b8897..3a3710c6e6e 100644 --- a/src/test/java/org/prebid/server/it/ApplicationTest.java +++ b/src/test/java/org/prebid/server/it/ApplicationTest.java @@ -53,6 +53,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.equalToIgnoreCase; import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static io.restassured.RestAssured.given; @@ -397,7 +398,7 @@ public void getuidsShouldReturnJsonWithUids() throws JSONException, IOException } @Test - public void vtrackShouldReturnJsonWithUids() throws JSONException, IOException { + public void vtrackShouldPutBidsAndReturnUids() throws JSONException, IOException { // given and when WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/cache")) .withRequestBody(equalToBidCacheRequest(jsonFrom("vtrack/test-cache-request.json"))) @@ -413,6 +414,24 @@ public void vtrackShouldReturnJsonWithUids() throws JSONException, IOException { assertJsonEquals("vtrack/test-vtrack-response.json", response, emptyList()); } + @Test + public void vtrackShouldReturnCachedObjects() throws IOException { + // given and when + WIRE_MOCK_RULE.stubFor(get(urlPathEqualTo("/cache")) + .withQueryParam("uuid", equalTo("key")) + .withQueryParam("ch", equalTo("test.com")) + .willReturn(aResponse().withBody(xmlFrom("vtrack/test-vtrack-response.xml")))); + + final Response response = given(SPEC) + .when() + .queryParam("uuid", "key") + .queryParam("ch", "test.com") + .get("/vtrack"); + + // then + assertThat(response.asString()).isEqualTo(xmlFrom("vtrack/test-vtrack-response.xml")); + } + @Test public void optionsRequestShouldRespondWithOriginalPolicyHeaders() { // when diff --git a/src/test/java/org/prebid/server/it/IntegrationTest.java b/src/test/java/org/prebid/server/it/IntegrationTest.java index 7df8040f599..7b8d42e0dc0 100644 --- a/src/test/java/org/prebid/server/it/IntegrationTest.java +++ b/src/test/java/org/prebid/server/it/IntegrationTest.java @@ -40,6 +40,8 @@ import org.springframework.test.context.TestPropertySource; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @@ -118,6 +120,12 @@ protected static String jsonFrom(String file, PrebidVersionProvider prebidVersio .replace("{{ pbs.java.version }}", prebidVersionProvider.getNameVersionRecord()); } + protected static String xmlFrom(String file) throws IOException { + try (InputStream inputStream = IntegrationTest.class.getResourceAsStream(file)) { + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + } + protected static String openrtbAuctionResponseFrom(String templatePath, Response response, List bidders) throws IOException { diff --git a/src/test/resources/org/prebid/server/it/vtrack/test-vtrack-response.xml b/src/test/resources/org/prebid/server/it/vtrack/test-vtrack-response.xml new file mode 100644 index 00000000000..19e3ace7ba3 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/vtrack/test-vtrack-response.xml @@ -0,0 +1,12 @@ + + + + prebid.org wrapper + + + + + + + + From fa4b64da67e37b0ad2ab5109d771a03011d1ad3d Mon Sep 17 00:00:00 2001 From: antonbabak Date: Mon, 21 Jul 2025 12:18:43 +0200 Subject: [PATCH 02/10] Add metrics --- .../prebid/server/cache/CoreCacheService.java | 48 +++++--- .../prebid/server/metric/CacheMetrics.java | 7 ++ .../server/metric/CacheReadMetrics.java | 20 +++ .../server/metric/CacheVtrackMetrics.java | 39 ++++++ .../server/metric/CacheWriteMetrics.java | 19 +++ .../org/prebid/server/metric/Metrics.java | 16 ++- .../server/cache/CoreCacheServiceTest.java | 115 +++++++++++++++++- .../org/prebid/server/metric/MetricsTest.java | 31 ++++- 8 files changed, 266 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/prebid/server/metric/CacheReadMetrics.java create mode 100644 src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java create mode 100644 src/main/java/org/prebid/server/metric/CacheWriteMetrics.java diff --git a/src/main/java/org/prebid/server/cache/CoreCacheService.java b/src/main/java/org/prebid/server/cache/CoreCacheService.java index ea1ea41fe29..464bc29fdfc 100644 --- a/src/main/java/org/prebid/server/cache/CoreCacheService.java +++ b/src/main/java/org/prebid/server/cache/CoreCacheService.java @@ -191,9 +191,20 @@ private Future makeRequest(BidCacheRequest bidCacheRequest, cacheHeaders, mapper.encodeToString(bidCacheRequest), remainingTimeout) - .map(response -> toBidCacheResponse( + .map(response -> processVtrackWriteCacheResponse( response.getStatusCode(), response.getBody(), bidCount, accountId, startTime)) - .recover(exception -> failResponse(exception, accountId, startTime)); + .recover(exception -> failVtrackCacheWriteResponse(exception, accountId, startTime)); + } + + private BidCacheResponse processVtrackWriteCacheResponse(int statusCode, + String responseBody, + int bidCount, + String accountId, + long startTime) { + + final BidCacheResponse bidCacheResponse = toBidCacheResponse(statusCode, responseBody, bidCount); + metrics.updateVtrackCacheWriteRequestTime(accountId, clock.millis() - startTime, MetricName.ok); + return bidCacheResponse; } public Future cachePutObjects(List bidPutObjects, @@ -339,8 +350,8 @@ private CacheServiceResult processResponseOpenrtb(HttpClientResponse response, externalEndpointUrl.toString(), httpRequest, httpResponse, startTime); final BidCacheResponse bidCacheResponse; try { - bidCacheResponse = toBidCacheResponse( - responseStatusCode, response.getBody(), bidCount, accountId, startTime); + bidCacheResponse = toBidCacheResponse(responseStatusCode, response.getBody(), bidCount); + metrics.updateAuctionCacheRequestTime(accountId, clock.millis() - startTime, MetricName.ok); } catch (PreBidException e) { return CacheServiceResult.of(httpCall, e, Collections.emptyMap()); } @@ -357,7 +368,7 @@ private CacheServiceResult failResponseOpenrtb(Throwable exception, logger.warn("Error occurred while interacting with cache service: {}", exception.getMessage()); logger.debug("Error occurred while interacting with cache service", exception); - metrics.updateCacheRequestFailedTime(accountId, clock.millis() - startTime); + metrics.updateAuctionCacheRequestTime(accountId, clock.millis() - startTime, MetricName.err); final DebugHttpCall httpCall = makeDebugHttpCall(externalEndpointUrl.toString(), request, null, startTime); return CacheServiceResult.of(httpCall, exception, Collections.emptyMap()); @@ -456,9 +467,7 @@ private String generateWinUrl(String bidId, private BidCacheResponse toBidCacheResponse(int statusCode, String responseBody, - int bidCount, - String accountId, - long startTime) { + int bidCount) { if (statusCode != 200) { throw new PreBidException("HTTP status code " + statusCode); @@ -476,7 +485,6 @@ private BidCacheResponse toBidCacheResponse(int statusCode, throw new PreBidException("The number of response cache objects doesn't match with bids"); } - metrics.updateCacheRequestSuccessTime(accountId, clock.millis() - startTime); return bidCacheResponse; } @@ -643,29 +651,41 @@ public Future getCachedObject(String key, String ch, Timeout return Future.failedFuture(new IllegalArgumentException("Configured cache url is malformed", e)); } + final long startTime = clock.millis(); return httpClient.get(url, cacheHeaders, remainingTimeout) - .map(this::handleResponse) - .recover(CoreCacheService::failResponse); + .map(response -> processVtrackReadResponse(response, startTime)) + .recover(exception -> failVtrackCacheReadResponse(exception, startTime)); } - private HttpClientResponse handleResponse(HttpClientResponse response) { + private HttpClientResponse processVtrackReadResponse(HttpClientResponse response, long startTime) { final int statusCode = response.getStatusCode(); final String body = response.getBody(); if (statusCode == 200) { + metrics.updateVtrackCacheReadRequestTime(clock.millis() - startTime, MetricName.ok); return response; } try { final CacheErrorResponse errorResponse = mapper.decodeValue(body, CacheErrorResponse.class); + metrics.updateVtrackCacheReadRequestTime(clock.millis() - startTime, MetricName.err); return HttpClientResponse.of(statusCode, response.getHeaders(), errorResponse.getMessage()); } catch (DecodeException e) { throw new PreBidException("Cannot parse response: " + body, e); } } - private Future failResponse(Throwable exception, String accountId, long startTime) { - metrics.updateCacheRequestFailedTime(accountId, clock.millis() - startTime); + private Future failVtrackCacheWriteResponse(Throwable exception, String accountId, long startTime) { + if (exception instanceof PreBidException) { + metrics.updateVtrackCacheWriteRequestTime(accountId, clock.millis() - startTime, MetricName.err); + } + return failResponse(exception); + } + + private Future failVtrackCacheReadResponse(Throwable exception, long startTime) { + if (exception instanceof PreBidException) { + metrics.updateVtrackCacheReadRequestTime(clock.millis() - startTime, MetricName.err); + } return failResponse(exception); } diff --git a/src/main/java/org/prebid/server/metric/CacheMetrics.java b/src/main/java/org/prebid/server/metric/CacheMetrics.java index 5116e10e08f..4838c0848f6 100644 --- a/src/main/java/org/prebid/server/metric/CacheMetrics.java +++ b/src/main/java/org/prebid/server/metric/CacheMetrics.java @@ -13,6 +13,7 @@ class CacheMetrics extends UpdatableMetrics { private final RequestMetrics requestsMetrics; private final CacheCreativeSizeMetrics cacheCreativeSizeMetrics; private final CacheCreativeTtlMetrics cacheCreativeTtlMetrics; + private final CacheVtrackMetrics cacheVtrackMetrics; CacheMetrics(MetricRegistry metricRegistry, CounterType counterType) { super( @@ -23,6 +24,7 @@ class CacheMetrics extends UpdatableMetrics { requestsMetrics = new RequestMetrics(metricRegistry, counterType, createPrefix()); cacheCreativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix()); cacheCreativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix()); + cacheVtrackMetrics = new CacheVtrackMetrics(metricRegistry, counterType, createPrefix()); } CacheMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { @@ -34,6 +36,7 @@ class CacheMetrics extends UpdatableMetrics { requestsMetrics = new RequestMetrics(metricRegistry, counterType, createPrefix(prefix)); cacheCreativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix(prefix)); cacheCreativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix(prefix)); + cacheVtrackMetrics = new CacheVtrackMetrics(metricRegistry, counterType, createPrefix(prefix)); } private static String createPrefix(String prefix) { @@ -59,4 +62,8 @@ CacheCreativeSizeMetrics creativeSize() { CacheCreativeTtlMetrics creativeTtl() { return cacheCreativeTtlMetrics; } + + CacheVtrackMetrics vtrack() { + return cacheVtrackMetrics; + } } diff --git a/src/main/java/org/prebid/server/metric/CacheReadMetrics.java b/src/main/java/org/prebid/server/metric/CacheReadMetrics.java new file mode 100644 index 00000000000..356d96fb9d1 --- /dev/null +++ b/src/main/java/org/prebid/server/metric/CacheReadMetrics.java @@ -0,0 +1,20 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; + +import java.util.Objects; +import java.util.function.Function; + +public class CacheReadMetrics extends UpdatableMetrics { + + CacheReadMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { + super(Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(Objects.requireNonNull(prefix))); + } + + private static Function nameCreator(String prefix) { + return metricName -> "%s.read.%s".formatted(prefix, metricName); + } + +} diff --git a/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java b/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java new file mode 100644 index 00000000000..759f16aabac --- /dev/null +++ b/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java @@ -0,0 +1,39 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; + +import java.util.Objects; +import java.util.function.Function; + +class CacheVtrackMetrics extends UpdatableMetrics { + + private final CacheReadMetrics readMetrics; + private final CacheWriteMetrics writeMetrics; + + CacheVtrackMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { + super( + Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(createPrefix(Objects.requireNonNull(prefix)))); + + readMetrics = new CacheReadMetrics(metricRegistry, counterType, createPrefix(prefix)); + writeMetrics = new CacheWriteMetrics(metricRegistry, counterType, createPrefix(prefix)); + } + + private static Function nameCreator(String prefix) { + return metricName -> "%s.%s".formatted(prefix, metricName); + } + + private static String createPrefix(String prefix) { + return prefix + ".vtrack"; + } + + CacheReadMetrics read() { + return readMetrics; + } + + CacheWriteMetrics write() { + return writeMetrics; + } + +} diff --git a/src/main/java/org/prebid/server/metric/CacheWriteMetrics.java b/src/main/java/org/prebid/server/metric/CacheWriteMetrics.java new file mode 100644 index 00000000000..d0d598d6ae5 --- /dev/null +++ b/src/main/java/org/prebid/server/metric/CacheWriteMetrics.java @@ -0,0 +1,19 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; + +import java.util.Objects; +import java.util.function.Function; + +public class CacheWriteMetrics extends UpdatableMetrics { + + CacheWriteMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { + super(Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(Objects.requireNonNull(prefix))); + } + + private static Function nameCreator(String prefix) { + return metricName -> "%s.write.%s".formatted(prefix, metricName); + } +} diff --git a/src/main/java/org/prebid/server/metric/Metrics.java b/src/main/java/org/prebid/server/metric/Metrics.java index 00295decad3..41a828ed46c 100644 --- a/src/main/java/org/prebid/server/metric/Metrics.java +++ b/src/main/java/org/prebid/server/metric/Metrics.java @@ -606,14 +606,18 @@ public void updateStoredImpsMetric(boolean found) { } } - public void updateCacheRequestSuccessTime(String accountId, long timeElapsed) { - cache().requests().updateTimer(MetricName.ok, timeElapsed); - forAccount(accountId).cache().requests().updateTimer(MetricName.ok, timeElapsed); + public void updateVtrackCacheReadRequestTime(long timeElapsed, MetricName metricName) { + cache().vtrack().read().updateTimer(metricName, timeElapsed); } - public void updateCacheRequestFailedTime(String accountId, long timeElapsed) { - cache().requests().updateTimer(MetricName.err, timeElapsed); - forAccount(accountId).cache().requests().updateTimer(MetricName.err, timeElapsed); + public void updateVtrackCacheWriteRequestTime(String accountId, long timeElapsed, MetricName metricName) { + cache().vtrack().write().updateTimer(metricName, timeElapsed); + forAccount(accountId).cache().vtrack().write().updateTimer(metricName, timeElapsed); + } + + public void updateAuctionCacheRequestTime(String accountId, long timeElapsed, MetricName metricName) { + cache().requests().updateTimer(metricName, timeElapsed); + forAccount(accountId).cache().requests().updateTimer(metricName, timeElapsed); } public void updateCacheCreativeSize(String accountId, int creativeSize, MetricName creativeType) { diff --git a/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java b/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java index 0697e0c8b51..ca2ee9b20cd 100644 --- a/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java +++ b/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java @@ -57,6 +57,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeoutException; import java.util.function.UnaryOperator; import static java.util.Arrays.asList; @@ -234,7 +235,7 @@ public void cacheBidsOpenrtbShouldTolerateReadingHttpResponseFails() throws Json eventsContext); // then - verify(metrics).updateCacheRequestFailedTime(eq("accountId"), anyLong()); + verify(metrics).updateAuctionCacheRequestTime(eq("accountId"), anyLong(), eq(MetricName.err)); verify(httpClient).post(eq("http://cache-service/cache"), any(), any(), anyLong()); final CacheServiceResult result = future.result(); @@ -287,7 +288,7 @@ public void cacheBidsOpenrtbShouldTryCallingInternalEndpointAndTolerateReadingHt eventsContext); // then - verify(metrics).updateCacheRequestFailedTime(eq("accountId"), anyLong()); + verify(metrics).updateAuctionCacheRequestTime(eq("accountId"), anyLong(), eq(MetricName.err)); verify(httpClient).post(eq("http://cache-service-internal/cache"), any(), any(), anyLong()); final CacheServiceResult result = future.result(); @@ -508,6 +509,10 @@ public void cacheBidsOpenrtbShouldPerformHttpRequestWithExpectedBody() throws IO .auctionTimestamp(1000L) .build(); + given(httpClient.post(anyString(), any(), any(), anyLong())).willReturn(Future.succeededFuture( + HttpClientResponse.of(200, null, mapper.writeValueAsString(BidCacheResponse.of( + List.of(CacheObject.of("uuid1"), CacheObject.of("uuid2"), CacheObject.of("uuid3"))))))); + // when target.cacheBidsOpenrtb( asList(bidInfo1, bidInfo2), @@ -527,6 +532,8 @@ public void cacheBidsOpenrtbShouldPerformHttpRequestWithExpectedBody() throws IO verify(metrics).updateCacheCreativeTtl(eq("accountId"), eq(1), eq(MetricName.json)); verify(metrics).updateCacheCreativeTtl(eq("accountId"), eq(2), eq(MetricName.json)); + verify(metrics).updateAuctionCacheRequestTime(eq("accountId"), anyLong(), eq(MetricName.ok)); + final Bid bid1 = bidInfo1.getBid(); final Bid bid2 = bidInfo2.getBid(); @@ -798,7 +805,7 @@ public void cachePutObjectsShouldReturnResultWithEmptyListWhenPutObjectsIsEmpty( } @Test - public void cachePutObjectsShould() throws IOException { + public void cachePutObjectsShouldCacheObjects() throws IOException { // given final BidPutObject firstBidPutObject = BidPutObject.builder() .type("json") @@ -830,6 +837,10 @@ public void cachePutObjectsShould() throws IOException { .willReturn(new TextNode("VAST")) .willReturn(new TextNode("updatedVast")); + given(httpClient.post(anyString(), any(), any(), anyLong())).willReturn(Future.succeededFuture( + HttpClientResponse.of(200, null, mapper.writeValueAsString(BidCacheResponse.of( + List.of(CacheObject.of("uuid1"), CacheObject.of("uuid2"), CacheObject.of("uuid3"))))))); + // when target.cachePutObjects( asList(firstBidPutObject, secondBidPutObject, thirdBidPutObject), @@ -850,6 +861,8 @@ public void cachePutObjectsShould() throws IOException { verify(metrics).updateCacheCreativeTtl(eq("account"), eq(2), eq(MetricName.xml)); verify(metrics).updateCacheCreativeTtl(eq("account"), eq(3), eq(MetricName.unknown)); + verify(metrics).updateVtrackCacheWriteRequestTime(eq("account"), anyLong(), eq(MetricName.ok)); + verify(vastModifier).modifyVastXml(true, singleton("bidder1"), firstBidPutObject, "account", "pbjs"); verify(vastModifier).modifyVastXml(true, singleton("bidder1"), secondBidPutObject, "account", "pbjs"); @@ -875,6 +888,78 @@ public void cachePutObjectsShould() throws IOException { .containsExactly(modifiedFirstBidPutObject, modifiedSecondBidPutObject, modifiedThirdBidPutObject); } + @Test + public void cachePutObjectsShouldLogErrorMetricsWhenStatusCodeIsNotOk() { + // given + final BidPutObject bidObject = BidPutObject.builder() + .type("json") + .bidid("bidId1") + .bidder("bidder1") + .timestamp(1L) + .value(new TextNode("vast")) + .ttlseconds(1) + .build(); + + given(vastModifier.modifyVastXml(any(), any(), any(), any(), anyString())) + .willReturn(new TextNode("modifiedVast")) + .willReturn(new TextNode("VAST")) + .willReturn(new TextNode("updatedVast")); + + given(httpClient.post(eq("http://cache-service/cache"), any(), any(), anyLong())) + .willReturn(Future.succeededFuture(HttpClientResponse.of(404, null, null))); + + // when + target.cachePutObjects( + singletonList(bidObject), + true, + singleton("bidder1"), + "account", + "pbjs", + timeout); + + // then + verify(metrics).updateCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); + verify(metrics).updateCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); + verify(metrics).updateVtrackCacheWriteRequestTime(eq("account"), anyLong(), eq(MetricName.err)); + verify(vastModifier).modifyVastXml(true, singleton("bidder1"), bidObject, "account", "pbjs"); + } + + @Test + public void cachePutObjectsShouldNotLogErrorMetricsWhenCacheServiceIsNotConnected() { + // given + final BidPutObject bidObject = BidPutObject.builder() + .type("json") + .bidid("bidId1") + .bidder("bidder1") + .timestamp(1L) + .value(new TextNode("vast")) + .ttlseconds(1) + .build(); + + given(vastModifier.modifyVastXml(any(), any(), any(), any(), anyString())) + .willReturn(new TextNode("modifiedVast")) + .willReturn(new TextNode("VAST")) + .willReturn(new TextNode("updatedVast")); + + given(httpClient.post(eq("http://cache-service/cache"), any(), any(), anyLong())) + .willReturn(Future.failedFuture(new TimeoutException("Timeout"))); + + // when + target.cachePutObjects( + singletonList(bidObject), + true, + singleton("bidder1"), + "account", + "pbjs", + timeout); + + // then + verify(metrics, never()).updateVtrackCacheWriteRequestTime(eq("account"), anyLong(), any()); + verify(metrics).updateCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); + verify(metrics).updateCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); + verify(vastModifier).modifyVastXml(true, singleton("bidder1"), bidObject, "account", "pbjs"); + } + @Test public void cachePutObjectsShouldCallInternalCacheEndpointWhenProvided() throws IOException { // given @@ -1373,6 +1458,7 @@ public void getCachedObjectShouldAddUuidAndChQueryParamsBeforeSendingWhenChIsPre // then assertThat(result.result()).isEqualTo(response); + verify(metrics).updateVtrackCacheReadRequestTime(anyLong(), eq(MetricName.ok)); } @Test @@ -1391,6 +1477,7 @@ public void getCachedObjectShouldAddUuidQueryParamsBeforeSendingWhenChIsAbsent() // then assertThat(result.result()).isEqualTo(response); + verify(metrics).updateVtrackCacheReadRequestTime(anyLong(), eq(MetricName.ok)); } @Test @@ -1430,6 +1517,25 @@ public void getCachedObjectShouldAddUuidQueryParamsToInternalBeforeSendingWhenCh assertThat(result.result()).isEqualTo(response); } + @Test + public void getCachedObjectShouldNotLogErrorMetricsWhenCacheIsNotReached() { + // given + final HttpClientResponse response = HttpClientResponse.of( + 200, + MultiMap.caseInsensitiveMultiMap().add("Header", "Value"), + "body"); + + given(httpClient.get(eq("http://cache-service/cache?uuid=key&ch=ch"), any(), anyLong())) + .willReturn(Future.failedFuture(new TimeoutException("Timeout"))); + + // when + final Future result = target.getCachedObject("key", "ch", timeout); + + // then + assertThat(result.failed()).isTrue(); + verify(metrics, never()).updateVtrackCacheReadRequestTime(anyLong(), any()); + } + @Test public void getCachedObjectShouldHandleErrorResponse() { // given @@ -1446,6 +1552,7 @@ public void getCachedObjectShouldHandleErrorResponse() { // then assertThat(result.result()).isEqualTo(HttpClientResponse.of(404, null, "Resource not found")); + verify(metrics).updateVtrackCacheReadRequestTime(anyLong(), eq(MetricName.err)); } @Test @@ -1463,6 +1570,8 @@ public void getCachedObjectShouldFailWhenErrorResponseCanNotBeParsed() { assertThat(result.failed()).isTrue(); assertThat(result.cause()).hasMessage("Cannot parse response: Resource not found"); assertThat(result.cause()).isInstanceOf(PreBidException.class); + + verify(metrics).updateVtrackCacheReadRequestTime(anyLong(), eq(MetricName.err)); } private AuctionContext givenAuctionContext(UnaryOperator accountCustomizer, diff --git a/src/test/java/org/prebid/server/metric/MetricsTest.java b/src/test/java/org/prebid/server/metric/MetricsTest.java index 243ef33e66d..3d7625dc7f8 100644 --- a/src/test/java/org/prebid/server/metric/MetricsTest.java +++ b/src/test/java/org/prebid/server/metric/MetricsTest.java @@ -1218,23 +1218,42 @@ public void shouldIncrementStoredImpMissingMetric() { } @Test - public void shouldIncrementPrebidCacheRequestSuccessTimer() { + public void shouldIncrementAuctionPrebidCacheRequestTimer() { // when - metrics.updateCacheRequestSuccessTime("accountId", 1424L); + metrics.updateAuctionCacheRequestTime("accountId", 1424L, MetricName.ok); + metrics.updateAuctionCacheRequestTime("accountId", 1424L, MetricName.err); // then assertThat(metricRegistry.timer("prebid_cache.requests.ok").getCount()).isEqualTo(1); assertThat(metricRegistry.timer("account.accountId.prebid_cache.requests.ok").getCount()).isOne(); + + assertThat(metricRegistry.timer("prebid_cache.requests.err").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("account.accountId.prebid_cache.requests.err").getCount()).isOne(); } @Test - public void shouldIncrementPrebidCacheRequestFailedTimer() { + public void shouldIncrementVtrackReadPrebidCacheRequestTimer() { // when - metrics.updateCacheRequestFailedTime("accountId", 1424L); + metrics.updateVtrackCacheReadRequestTime(1424L, MetricName.ok); + metrics.updateVtrackCacheReadRequestTime(1424L, MetricName.err); // then - assertThat(metricRegistry.timer("prebid_cache.requests.err").getCount()).isEqualTo(1); - assertThat(metricRegistry.timer("account.accountId.prebid_cache.requests.err").getCount()).isOne(); + assertThat(metricRegistry.timer("prebid_cache.vtrack.read.ok").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("prebid_cache.vtrack.read.err").getCount()).isEqualTo(1); + } + + @Test + public void shouldIncrementVtrackWritePrebidCacheRequestTimer() { + // when + metrics.updateVtrackCacheWriteRequestTime("accountId", 1424L, MetricName.ok); + metrics.updateVtrackCacheWriteRequestTime("accountId", 1424L, MetricName.err); + + // then + assertThat(metricRegistry.timer("prebid_cache.vtrack.write.ok").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("account.accountId.prebid_cache.vtrack.write.ok").getCount()).isOne(); + + assertThat(metricRegistry.timer("prebid_cache.vtrack.write.err").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("account.accountId.prebid_cache.vtrack.write.err").getCount()).isOne(); } @Test From 98c48bc2eeb301bcfbafc062cc5c7090bd1c4e89 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 21 Aug 2025 11:28:53 +0200 Subject: [PATCH 03/10] Add creative_size and creative_ttl metrics for Vtrack --- .../prebid/server/cache/CoreCacheService.java | 20 +++-- .../server/metric/CacheVtrackMetrics.java | 12 +++ .../org/prebid/server/metric/Metrics.java | 20 +++-- .../server/cache/CoreCacheServiceTest.java | 24 +++--- .../org/prebid/server/metric/MetricsTest.java | 76 ++++++++++++++----- 5 files changed, 111 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/prebid/server/cache/CoreCacheService.java b/src/main/java/org/prebid/server/cache/CoreCacheService.java index 464bc29fdfc..e3769216570 100644 --- a/src/main/java/org/prebid/server/cache/CoreCacheService.java +++ b/src/main/java/org/prebid/server/cache/CoreCacheService.java @@ -58,6 +58,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -217,7 +218,10 @@ public Future cachePutObjects(List bidPutObjects final List cachedCreatives = updatePutObjects(bidPutObjects, isEventsEnabled, biddersAllowingVastUpdate, accountId, integration); - updateCreativeMetrics(accountId, cachedCreatives); + updateCreativeMetrics( + cachedCreatives, + (ttl, type) -> metrics.updateVtrackCacheCreativeTtl(accountId, ttl, type), + (size, type) -> metrics.updateVtrackCacheCreativeSize(accountId, size, type)); return makeRequest(toBidCacheRequest(cachedCreatives), cachedCreatives.size(), timeout, accountId); } @@ -316,7 +320,10 @@ private Future doCacheOpenrtb(List bids, final BidCacheRequest bidCacheRequest = toBidCacheRequest(cachedCreatives); - updateCreativeMetrics(accountId, cachedCreatives); + updateCreativeMetrics( + cachedCreatives, + (ttl, type) -> metrics.updateCacheCreativeTtl(accountId, ttl, type), + (size, type) -> metrics.updateCacheCreativeSize(accountId, size, type)); final String url = ObjectUtils.firstNonNull(internalEndpointUrl, externalEndpointUrl).toString(); final String body = mapper.encodeToString(bidCacheRequest); @@ -542,17 +549,20 @@ private static String resolveVideoBidUuid(String uuid, String hbCacheId) { return hbCacheId != null && uuid.endsWith(hbCacheId) ? hbCacheId : uuid; } - private void updateCreativeMetrics(String accountId, List cachedCreatives) { + private void updateCreativeMetrics(List cachedCreatives, + BiConsumer updateCreativeTtlMetric, + BiConsumer updateCreativeSiseMetric) { + for (CachedCreative cachedCreative : cachedCreatives) { final BidPutObject payload = cachedCreative.getPayload(); final MetricName creativeType = resolveCreativeTypeName(payload); final Integer creativeTtl = ObjectUtils.defaultIfNull(payload.getTtlseconds(), payload.getExpiry()); if (creativeTtl != null) { - metrics.updateCacheCreativeTtl(accountId, creativeTtl, creativeType); + updateCreativeTtlMetric.accept(creativeTtl, creativeType); } - metrics.updateCacheCreativeSize(accountId, cachedCreative.getSize(), creativeType); + updateCreativeSiseMetric.accept(cachedCreative.getSize(), creativeType); } } diff --git a/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java b/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java index 759f16aabac..ba1644fa136 100644 --- a/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java +++ b/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java @@ -9,6 +9,8 @@ class CacheVtrackMetrics extends UpdatableMetrics { private final CacheReadMetrics readMetrics; private final CacheWriteMetrics writeMetrics; + private final CacheCreativeSizeMetrics creativeSizeMetrics; + private final CacheCreativeTtlMetrics creativeTtlMetrics; CacheVtrackMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { super( @@ -18,6 +20,8 @@ class CacheVtrackMetrics extends UpdatableMetrics { readMetrics = new CacheReadMetrics(metricRegistry, counterType, createPrefix(prefix)); writeMetrics = new CacheWriteMetrics(metricRegistry, counterType, createPrefix(prefix)); + creativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix(prefix)); + creativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix(prefix)); } private static Function nameCreator(String prefix) { @@ -36,4 +40,12 @@ CacheWriteMetrics write() { return writeMetrics; } + CacheCreativeSizeMetrics creativeSize() { + return creativeSizeMetrics; + } + + CacheCreativeTtlMetrics creativeTtl() { + return creativeTtlMetrics; + } + } diff --git a/src/main/java/org/prebid/server/metric/Metrics.java b/src/main/java/org/prebid/server/metric/Metrics.java index 81c60deaaee..5a507da5021 100644 --- a/src/main/java/org/prebid/server/metric/Metrics.java +++ b/src/main/java/org/prebid/server/metric/Metrics.java @@ -619,6 +619,16 @@ public void updateVtrackCacheWriteRequestTime(String accountId, long timeElapsed forAccount(accountId).cache().vtrack().write().updateTimer(metricName, timeElapsed); } + public void updateVtrackCacheCreativeSize(String accountId, int creativeSize, MetricName creativeType) { + cache().vtrack().creativeSize().updateHistogram(creativeType, creativeSize); + forAccount(accountId).cache().vtrack().creativeSize().updateHistogram(creativeType, creativeSize); + } + + public void updateVtrackCacheCreativeTtl(String accountId, Integer creativeTtl, MetricName creativeType) { + cache().vtrack().creativeTtl().updateHistogram(creativeType, creativeTtl); + forAccount(accountId).cache().vtrack().creativeTtl().updateHistogram(creativeType, creativeTtl); + } + public void updateAuctionCacheRequestTime(String accountId, long timeElapsed, MetricName metricName) { cache().requests().updateTimer(metricName, timeElapsed); forAccount(accountId).cache().requests().updateTimer(metricName, timeElapsed); @@ -629,6 +639,11 @@ public void updateCacheCreativeSize(String accountId, int creativeSize, MetricNa forAccount(accountId).cache().creativeSize().updateHistogram(creativeType, creativeSize); } + public void updateCacheCreativeTtl(String accountId, Integer creativeTtl, MetricName creativeType) { + cache().creativeTtl().updateHistogram(creativeType, creativeTtl); + forAccount(accountId).cache().creativeTtl().updateHistogram(creativeType, creativeTtl); + } + public void updateTimeoutNotificationMetric(boolean success) { if (success) { timeoutNotificationMetrics.incCounter(MetricName.ok); @@ -706,11 +721,6 @@ public void updateAccountModuleDurationMetric(Account account, String moduleCode } } - public void updateCacheCreativeTtl(String accountId, Integer creativeTtl, MetricName creativeType) { - cache().creativeTtl().updateHistogram(creativeType, creativeTtl); - forAccount(accountId).cache().creativeTtl().updateHistogram(creativeType, creativeTtl); - } - public void updateRequestsActivityDisallowedCount(Activity activity) { requests().activities().forActivity(activity).incCounter(MetricName.disallowed_count); } diff --git a/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java b/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java index ca2ee9b20cd..4a9fbabc15a 100644 --- a/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java +++ b/src/test/java/org/prebid/server/cache/CoreCacheServiceTest.java @@ -853,13 +853,13 @@ public void cachePutObjectsShouldCacheObjects() throws IOException { // then verify(httpClient).post(eq("http://cache-service/cache"), any(), any(), anyLong()); - verify(metrics).updateCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); - verify(metrics).updateCacheCreativeSize(eq("account"), eq(4), eq(MetricName.xml)); - verify(metrics).updateCacheCreativeSize(eq("account"), eq(11), eq(MetricName.unknown)); + verify(metrics).updateVtrackCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); + verify(metrics).updateVtrackCacheCreativeSize(eq("account"), eq(4), eq(MetricName.xml)); + verify(metrics).updateVtrackCacheCreativeSize(eq("account"), eq(11), eq(MetricName.unknown)); - verify(metrics).updateCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); - verify(metrics).updateCacheCreativeTtl(eq("account"), eq(2), eq(MetricName.xml)); - verify(metrics).updateCacheCreativeTtl(eq("account"), eq(3), eq(MetricName.unknown)); + verify(metrics).updateVtrackCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); + verify(metrics).updateVtrackCacheCreativeTtl(eq("account"), eq(2), eq(MetricName.xml)); + verify(metrics).updateVtrackCacheCreativeTtl(eq("account"), eq(3), eq(MetricName.unknown)); verify(metrics).updateVtrackCacheWriteRequestTime(eq("account"), anyLong(), eq(MetricName.ok)); @@ -918,8 +918,8 @@ public void cachePutObjectsShouldLogErrorMetricsWhenStatusCodeIsNotOk() { timeout); // then - verify(metrics).updateCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); - verify(metrics).updateCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); + verify(metrics).updateVtrackCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); + verify(metrics).updateVtrackCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); verify(metrics).updateVtrackCacheWriteRequestTime(eq("account"), anyLong(), eq(MetricName.err)); verify(vastModifier).modifyVastXml(true, singleton("bidder1"), bidObject, "account", "pbjs"); } @@ -955,8 +955,8 @@ public void cachePutObjectsShouldNotLogErrorMetricsWhenCacheServiceIsNotConnecte // then verify(metrics, never()).updateVtrackCacheWriteRequestTime(eq("account"), anyLong(), any()); - verify(metrics).updateCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); - verify(metrics).updateCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); + verify(metrics).updateVtrackCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); + verify(metrics).updateVtrackCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); verify(vastModifier).modifyVastXml(true, singleton("bidder1"), bidObject, "account", "pbjs"); } @@ -997,8 +997,8 @@ public void cachePutObjectsShouldCallInternalCacheEndpointWhenProvided() throws // then verify(httpClient).post(eq("http://cache-service-internal/cache"), any(), any(), anyLong()); - verify(metrics).updateCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); - verify(metrics).updateCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); + verify(metrics).updateVtrackCacheCreativeSize(eq("account"), eq(12), eq(MetricName.json)); + verify(metrics).updateVtrackCacheCreativeTtl(eq("account"), eq(1), eq(MetricName.json)); verify(vastModifier).modifyVastXml(true, singleton("bidder1"), firstBidPutObject, "account", "pbjs"); diff --git a/src/test/java/org/prebid/server/metric/MetricsTest.java b/src/test/java/org/prebid/server/metric/MetricsTest.java index 657f81ff04c..f4ea9ff2a5e 100644 --- a/src/test/java/org/prebid/server/metric/MetricsTest.java +++ b/src/test/java/org/prebid/server/metric/MetricsTest.java @@ -1284,6 +1284,63 @@ public void shouldIncrementPrebidCacheCreativeSizeHistogram() { .isEqualTo(1); } + @Test + public void shouldIncrementPrebidCacheVtrackCreativeSizeHistogram() { + // when + metrics.updateVtrackCacheCreativeSize("accountId", 123, MetricName.json); + metrics.updateVtrackCacheCreativeSize("accountId", 456, MetricName.xml); + metrics.updateVtrackCacheCreativeSize("accountId", 789, MetricName.unknown); + + // then + assertThat(metricRegistry.histogram("prebid_cache.vtrack.creative_size.json").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("account.accountId.prebid_cache.vtrack.creative_size.json").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.vtrack.creative_size.xml").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("account.accountId.prebid_cache.vtrack.creative_size.xml").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.vtrack.creative_size.unknown").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("account.accountId.prebid_cache.vtrack.creative_size.unknown").getCount()) + .isEqualTo(1); + } + + @Test + public void shouldIncrementPrebidCacheCreativeTtlHistogram() { + // when + metrics.updateCacheCreativeTtl("accountId", 123, MetricName.json); + metrics.updateCacheCreativeTtl("accountId", 456, MetricName.xml); + metrics.updateCacheCreativeTtl("accountId", 789, MetricName.unknown); + + // then + assertThat(metricRegistry.histogram("prebid_cache.creative_ttl.json").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("account.accountId.prebid_cache.creative_ttl.json").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.creative_ttl.xml").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("account.accountId.prebid_cache.creative_ttl.xml").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.creative_ttl.unknown").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("account.accountId.prebid_cache.creative_ttl.unknown").getCount()) + .isEqualTo(1); + } + + @Test + public void shouldIncrementPrebidCacheVtrackCreativeTtlHistogram() { + // when + metrics.updateVtrackCacheCreativeTtl("accountId", 123, MetricName.json); + metrics.updateVtrackCacheCreativeTtl("accountId", 456, MetricName.xml); + metrics.updateVtrackCacheCreativeTtl("accountId", 789, MetricName.unknown); + + // then + assertThat(metricRegistry.histogram("prebid_cache.vtrack.creative_ttl.json").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("account.accountId.prebid_cache.vtrack.creative_ttl.json").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.vtrack.creative_ttl.xml").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("account.accountId.prebid_cache.vtrack.creative_ttl.xml").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.vtrack.creative_ttl.unknown").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("account.accountId.prebid_cache.vtrack.creative_ttl.unknown").getCount()) + .isEqualTo(1); + } + @Test public void shouldCreateCurrencyRatesGaugeMetric() { // when @@ -1571,25 +1628,6 @@ public void shouldIncrementUpdateAccountRequestRejectedByFailedFetchCount() { .isEqualTo(1); } - @Test - public void shouldIncrementPrebidCacheCreativeTtlHistogram() { - // when - metrics.updateCacheCreativeTtl("accountId", 123, MetricName.json); - metrics.updateCacheCreativeTtl("accountId", 456, MetricName.xml); - metrics.updateCacheCreativeTtl("accountId", 789, MetricName.unknown); - - // then - assertThat(metricRegistry.histogram("prebid_cache.creative_ttl.json").getCount()).isEqualTo(1); - assertThat(metricRegistry.histogram("account.accountId.prebid_cache.creative_ttl.json").getCount()) - .isEqualTo(1); - assertThat(metricRegistry.histogram("prebid_cache.creative_ttl.xml").getCount()).isEqualTo(1); - assertThat(metricRegistry.histogram("account.accountId.prebid_cache.creative_ttl.xml").getCount()) - .isEqualTo(1); - assertThat(metricRegistry.histogram("prebid_cache.creative_ttl.unknown").getCount()).isEqualTo(1); - assertThat(metricRegistry.histogram("account.accountId.prebid_cache.creative_ttl.unknown").getCount()) - .isEqualTo(1); - } - private void verifyCreatesConfiguredCounterType(Consumer metricsConsumer) { final EnumMap> counterTypeClasses = new EnumMap<>(CounterType.class); counterTypeClasses.put(CounterType.counter, Counter.class); From 2938275fd1590c724fccfa76e845a8670a5f7832 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 21 Aug 2025 12:55:30 +0200 Subject: [PATCH 04/10] Add Module Storage Metrics --- .../server/cache/BasicPbcStorageService.java | 47 +++++- .../prebid/server/metric/CacheMetrics.java | 7 + .../metric/CacheModuleStorageMetrics.java | 51 +++++++ .../org/prebid/server/metric/MetricName.java | 1 + .../org/prebid/server/metric/Metrics.java | 16 ++ .../spring/config/ServiceConfiguration.java | 8 +- .../cache/BasicPbcStorageServiceTest.java | 141 +++++++++++++++++- .../org/prebid/server/metric/MetricsTest.java | 54 +++++++ 8 files changed, 314 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/prebid/server/metric/CacheModuleStorageMetrics.java diff --git a/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java b/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java index 956330cd1ca..b38e8fc0f1c 100644 --- a/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java +++ b/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java @@ -11,10 +11,13 @@ import org.prebid.server.exception.PreBidException; import org.prebid.server.json.DecodeException; import org.prebid.server.json.JacksonMapper; +import org.prebid.server.metric.MetricName; +import org.prebid.server.metric.Metrics; import org.prebid.server.util.HttpUtil; import org.prebid.server.vertx.httpclient.HttpClient; import java.net.URL; +import java.time.Clock; import java.util.Objects; public class BasicPbcStorageService implements PbcStorageService { @@ -27,18 +30,24 @@ public class BasicPbcStorageService implements PbcStorageService { private final String apiKey; private final int callTimeoutMs; private final JacksonMapper mapper; + private final Clock clock; + private final Metrics metrics; public BasicPbcStorageService(HttpClient httpClient, URL endpointUrl, String apiKey, int callTimeoutMs, - JacksonMapper mapper) { + JacksonMapper mapper, + Clock clock, + Metrics metrics) { this.httpClient = Objects.requireNonNull(httpClient); this.endpointUrl = Objects.requireNonNull(endpointUrl); this.apiKey = Objects.requireNonNull(apiKey); this.callTimeoutMs = callTimeoutMs; this.mapper = Objects.requireNonNull(mapper); + this.clock = Objects.requireNonNull(clock); + this.metrics = metrics; } @Override @@ -55,20 +64,27 @@ public Future storeEntry(String key, return Future.failedFuture(e); } + final String valueToStore = prepareValueForStoring(value, type); final ModuleCacheRequest moduleCacheRequest = ModuleCacheRequest.of( constructEntryKey(key, appCode), type, - prepareValueForStoring(value, type), + valueToStore, application, ttlseconds); + updateCreativeMetrics(valueToStore, type, ttlseconds); + + final long startTime = clock.millis(); return httpClient.post( endpointUrl.toString(), securedCallHeaders(), mapper.encodeToString(moduleCacheRequest), callTimeoutMs) - .compose(response -> processStoreResponse(response.getStatusCode(), response.getBody())); + .compose(response -> processStoreResponse( + response.getStatusCode(), + response.getBody(), + startTime)); } @@ -99,6 +115,19 @@ private static void validateStoreData(String key, } } + private void updateCreativeMetrics(String value, StorageDataType type, Integer ttlseconds) { + final MetricName metricName = switch (type) { + case XML -> MetricName.xml; + case JSON -> MetricName.json; + case TEXT -> MetricName.text; + }; + + metrics.updateModuleStorageCacheCreativeSize(value.length(), metricName); + if (ttlseconds != null) { + metrics.updateModuleStorageCacheCreativeTtl(ttlseconds, metricName); + } + } + private static String prepareValueForStoring(String value, StorageDataType type) { return type == StorageDataType.TEXT ? new String(Base64.encodeBase64(value.getBytes())) @@ -114,12 +143,14 @@ private String constructEntryKey(String key, String moduleCode) { return MODULE_KEY_PREFIX + MODULE_KEY_DELIMETER + moduleCode + MODULE_KEY_DELIMETER + key; } - private Future processStoreResponse(int statusCode, String responseBody) { + private Future processStoreResponse(int statusCode, String responseBody, long startTime) { if (statusCode != 204) { + metrics.updateModuleStorageCacheWriteRequestTime(clock.millis() - startTime, MetricName.err); throw new PreBidException("HTTP status code: '%s', body: '%s' " .formatted(statusCode, responseBody)); } + metrics.updateModuleStorageCacheWriteRequestTime(clock.millis() - startTime, MetricName.ok); return Future.succeededFuture(); } @@ -134,11 +165,12 @@ public Future retrieveEntry(String key, return Future.failedFuture(e); } + final long startTime = clock.millis(); return httpClient.get( getRetrieveEndpoint(key, appCode, application), securedCallHeaders(), callTimeoutMs) - .map(response -> toModuleCacheResponse(response.getStatusCode(), response.getBody())); + .map(response -> toModuleCacheResponse(response.getStatusCode(), response.getBody(), startTime)); } @@ -165,11 +197,14 @@ private String getRetrieveEndpoint(String key, + "&a=" + application; } - private ModuleCacheResponse toModuleCacheResponse(int statusCode, String responseBody) { + private ModuleCacheResponse toModuleCacheResponse(int statusCode, String responseBody, long startTime) { if (statusCode != 200) { + metrics.updateModuleStorageCacheReadRequestTime(clock.millis() - startTime, MetricName.err); throw new PreBidException("HTTP status code " + statusCode); } + metrics.updateModuleStorageCacheReadRequestTime(clock.millis() - startTime, MetricName.ok); + final ModuleCacheResponse moduleCacheResponse; try { moduleCacheResponse = mapper.decodeValue(responseBody, ModuleCacheResponse.class); diff --git a/src/main/java/org/prebid/server/metric/CacheMetrics.java b/src/main/java/org/prebid/server/metric/CacheMetrics.java index 4838c0848f6..cb1b6326fd6 100644 --- a/src/main/java/org/prebid/server/metric/CacheMetrics.java +++ b/src/main/java/org/prebid/server/metric/CacheMetrics.java @@ -14,6 +14,7 @@ class CacheMetrics extends UpdatableMetrics { private final CacheCreativeSizeMetrics cacheCreativeSizeMetrics; private final CacheCreativeTtlMetrics cacheCreativeTtlMetrics; private final CacheVtrackMetrics cacheVtrackMetrics; + private final CacheModuleStorageMetrics cacheModuleStorageMetrics; CacheMetrics(MetricRegistry metricRegistry, CounterType counterType) { super( @@ -25,6 +26,7 @@ class CacheMetrics extends UpdatableMetrics { cacheCreativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix()); cacheCreativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix()); cacheVtrackMetrics = new CacheVtrackMetrics(metricRegistry, counterType, createPrefix()); + cacheModuleStorageMetrics = new CacheModuleStorageMetrics(metricRegistry, counterType, createPrefix()); } CacheMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { @@ -37,6 +39,7 @@ class CacheMetrics extends UpdatableMetrics { cacheCreativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix(prefix)); cacheCreativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix(prefix)); cacheVtrackMetrics = new CacheVtrackMetrics(metricRegistry, counterType, createPrefix(prefix)); + cacheModuleStorageMetrics = new CacheModuleStorageMetrics(metricRegistry, counterType, createPrefix(prefix)); } private static String createPrefix(String prefix) { @@ -66,4 +69,8 @@ CacheCreativeTtlMetrics creativeTtl() { CacheVtrackMetrics vtrack() { return cacheVtrackMetrics; } + + CacheModuleStorageMetrics moduleStorage() { + return cacheModuleStorageMetrics; + } } diff --git a/src/main/java/org/prebid/server/metric/CacheModuleStorageMetrics.java b/src/main/java/org/prebid/server/metric/CacheModuleStorageMetrics.java new file mode 100644 index 00000000000..ff93c5be3ec --- /dev/null +++ b/src/main/java/org/prebid/server/metric/CacheModuleStorageMetrics.java @@ -0,0 +1,51 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; + +import java.util.Objects; +import java.util.function.Function; + +class CacheModuleStorageMetrics extends UpdatableMetrics { + + private final CacheReadMetrics readMetrics; + private final CacheWriteMetrics writeMetrics; + private final CacheCreativeSizeMetrics creativeSizeMetrics; + private final CacheCreativeTtlMetrics creativeTtlMetrics; + + CacheModuleStorageMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { + super( + Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(createPrefix(Objects.requireNonNull(prefix)))); + + readMetrics = new CacheReadMetrics(metricRegistry, counterType, createPrefix(prefix)); + writeMetrics = new CacheWriteMetrics(metricRegistry, counterType, createPrefix(prefix)); + creativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix(prefix)); + creativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix(prefix)); + } + + private static Function nameCreator(String prefix) { + return metricName -> "%s.%s".formatted(prefix, metricName); + } + + private static String createPrefix(String prefix) { + return prefix + ".module_storage"; + } + + CacheReadMetrics read() { + return readMetrics; + } + + CacheWriteMetrics write() { + return writeMetrics; + } + + CacheCreativeSizeMetrics creativeSize() { + return creativeSizeMetrics; + } + + CacheCreativeTtlMetrics creativeTtl() { + return creativeTtlMetrics; + } + +} diff --git a/src/main/java/org/prebid/server/metric/MetricName.java b/src/main/java/org/prebid/server/metric/MetricName.java index ab32c446226..7e4af9ce724 100644 --- a/src/main/java/org/prebid/server/metric/MetricName.java +++ b/src/main/java/org/prebid/server/metric/MetricName.java @@ -121,6 +121,7 @@ public enum MetricName { // cache creative types json, xml, + text, // account.*.requests. rejected_by_invalid_account("rejected.invalid-account"), diff --git a/src/main/java/org/prebid/server/metric/Metrics.java b/src/main/java/org/prebid/server/metric/Metrics.java index 5a507da5021..0ec95d1b962 100644 --- a/src/main/java/org/prebid/server/metric/Metrics.java +++ b/src/main/java/org/prebid/server/metric/Metrics.java @@ -629,6 +629,22 @@ public void updateVtrackCacheCreativeTtl(String accountId, Integer creativeTtl, forAccount(accountId).cache().vtrack().creativeTtl().updateHistogram(creativeType, creativeTtl); } + public void updateModuleStorageCacheReadRequestTime(long timeElapsed, MetricName metricName) { + cache().moduleStorage().read().updateTimer(metricName, timeElapsed); + } + + public void updateModuleStorageCacheWriteRequestTime(long timeElapsed, MetricName metricName) { + cache().moduleStorage().write().updateTimer(metricName, timeElapsed); + } + + public void updateModuleStorageCacheCreativeSize(int creativeSize, MetricName creativeType) { + cache().moduleStorage().creativeSize().updateHistogram(creativeType, creativeSize); + } + + public void updateModuleStorageCacheCreativeTtl(Integer creativeTtl, MetricName creativeType) { + cache().moduleStorage().creativeTtl().updateHistogram(creativeType, creativeTtl); + } + public void updateAuctionCacheRequestTime(String accountId, long timeElapsed, MetricName metricName) { cache().requests().updateTimer(metricName, timeElapsed); forAccount(accountId).cache().requests().updateTimer(metricName, timeElapsed); diff --git a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java index aa57b0b04b7..ac7447baf29 100644 --- a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java @@ -247,14 +247,18 @@ PbcStorageService basicModuleCacheService( @Value("${storage.pbc.call-timeout-ms}") int callTimeoutMs, @Value("${pbc.api.key}") String apiKey, HttpClient httpClient, - JacksonMapper mapper) { + JacksonMapper mapper, + Clock clock, + Metrics metrics) { return new BasicPbcStorageService( httpClient, CacheServiceUtil.getCacheEndpointUrl(scheme, host, path), apiKey, callTimeoutMs, - mapper); + mapper, + clock, + metrics); } @Bean diff --git a/src/test/java/org/prebid/server/cache/BasicPbcStorageServiceTest.java b/src/test/java/org/prebid/server/cache/BasicPbcStorageServiceTest.java index c7b09518ae7..da8b0c500ad 100644 --- a/src/test/java/org/prebid/server/cache/BasicPbcStorageServiceTest.java +++ b/src/test/java/org/prebid/server/cache/BasicPbcStorageServiceTest.java @@ -15,20 +15,27 @@ import org.prebid.server.cache.proto.request.module.StorageDataType; import org.prebid.server.cache.proto.response.module.ModuleCacheResponse; import org.prebid.server.exception.PreBidException; +import org.prebid.server.metric.MetricName; +import org.prebid.server.metric.Metrics; import org.prebid.server.util.HttpUtil; import org.prebid.server.vertx.httpclient.HttpClient; import org.prebid.server.vertx.httpclient.model.HttpClientResponse; import java.net.MalformedURLException; import java.net.URL; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; @ExtendWith(MockitoExtension.class) public class BasicPbcStorageServiceTest extends VertxTest { @@ -36,6 +43,9 @@ public class BasicPbcStorageServiceTest extends VertxTest { @Mock(strictness = LENIENT) private HttpClient httpClient; + @Mock(strictness = LENIENT) + private Metrics metrics; + private BasicPbcStorageService target; @BeforeEach @@ -45,15 +55,40 @@ public void setUp() throws MalformedURLException, JsonProcessingException { new URL("http://cache-service/cache"), "pbc-api-key", 10, - jacksonMapper); + jacksonMapper, + Clock.fixed(Instant.now(), ZoneId.systemDefault()), + metrics); given(httpClient.post(anyString(), any(), any(), anyLong())).willReturn(Future.succeededFuture( - HttpClientResponse.of(200, null, "someBody"))); + HttpClientResponse.of(204, null, null))); given(httpClient.get(anyString(), any(), anyLong())).willReturn(Future.succeededFuture( HttpClientResponse.of(200, null, mapper.writeValueAsString( ModuleCacheResponse.of("some-key", StorageDataType.JSON, "some-value"))))); } + @Test + public void storeModuleEntryShouldReturnFailureResponse() { + //given + given(httpClient.post(anyString(), any(), any(), anyLong())).willReturn(Future.succeededFuture( + HttpClientResponse.of(500, null, null))); + + // when + target.storeEntry("some-key", + "some-value", + StorageDataType.TEXT, + 12, + "some-application", + "some-module-code"); + + // then + final ModuleCacheRequest result = captureModuleCacheRequest(); + assertThat(result.getKey()).isEqualTo("module.some-module-code.some-key"); + + verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); + verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.err)); + } + @Test public void storeModuleEntryShouldStoreExpectedKey() { // when @@ -67,6 +102,10 @@ public void storeModuleEntryShouldStoreExpectedKey() { // then final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getKey()).isEqualTo("module.some-module-code.some-key"); + + verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); + verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); } @Test @@ -82,6 +121,10 @@ public void storeModuleEntryShouldStoreExpectedValue() { // then final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getValue()).isEqualTo("c29tZS12YWx1ZQ=="); + + verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); + verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); } @Test @@ -97,10 +140,14 @@ public void storeModuleEntryShouldStoreExpectedApplication() { // then final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getApplication()).isEqualTo("some-application"); + + verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); + verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); } @Test - public void storeModuleEntryShouldStoreExpectedMediaType() { + public void storeModuleEntryShouldStoreTextMediaType() { // when target.storeEntry("some-key", "some-value", @@ -112,6 +159,48 @@ public void storeModuleEntryShouldStoreExpectedMediaType() { // then final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getType()).isEqualTo(StorageDataType.TEXT); + + verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); + verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + } + + @Test + public void storeModuleEntryShouldStoreXmlMediaType() { + // when + target.storeEntry("some-key", + "", + StorageDataType.XML, + 12, + "some-application", + "some-module-code"); + + // then + final ModuleCacheRequest result = captureModuleCacheRequest(); + assertThat(result.getType()).isEqualTo(StorageDataType.XML); + + verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.xml); + verify(metrics).updateModuleStorageCacheCreativeSize(13, MetricName.xml); + verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + } + + @Test + public void storeModuleEntryShouldStoreJsonMediaType() { + // when + target.storeEntry("some-key", + "{}", + StorageDataType.JSON, + 12, + "some-application", + "some-module-code"); + + // then + final ModuleCacheRequest result = captureModuleCacheRequest(); + assertThat(result.getType()).isEqualTo(StorageDataType.JSON); + + verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.json); + verify(metrics).updateModuleStorageCacheCreativeSize(2, MetricName.json); + verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); } @Test @@ -127,6 +216,10 @@ public void storeModuleEntryShouldStoreExpectedTtl() { // then final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getTtlseconds()).isEqualTo(12); + + verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); + verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); } @Test @@ -143,6 +236,8 @@ public void storeEntryShouldReturnFailedFutureIfKeyIsMissed() { assertThat(result.failed()).isTrue(); assertThat(result.cause()).isInstanceOf(PreBidException.class); assertThat(result.cause().getMessage()).isEqualTo("Module cache 'key' can not be blank"); + + verifyNoInteractions(metrics); } @Test @@ -159,6 +254,8 @@ public void storeEntryShouldReturnFailedFutureIfValueIsMissed() { assertThat(result.failed()).isTrue(); assertThat(result.cause()).isInstanceOf(PreBidException.class); assertThat(result.cause().getMessage()).isEqualTo("Module cache 'value' can not be blank"); + + verifyNoInteractions(metrics); } @Test @@ -175,6 +272,8 @@ public void storeEntryShouldReturnFailedFutureIfApplicationIsMissed() { assertThat(result.failed()).isTrue(); assertThat(result.cause()).isInstanceOf(PreBidException.class); assertThat(result.cause().getMessage()).isEqualTo("Module cache 'application' can not be blank"); + + verifyNoInteractions(metrics); } @Test @@ -191,6 +290,8 @@ public void storeEntryShouldReturnFailedFutureIfTypeIsMissed() { assertThat(result.failed()).isTrue(); assertThat(result.cause()).isInstanceOf(PreBidException.class); assertThat(result.cause().getMessage()).isEqualTo("Module cache 'type' can not be empty"); + + verifyNoInteractions(metrics); } @Test @@ -207,6 +308,8 @@ public void storeModuleEntryShouldReturnFailedFutureIfCodeIsMissed() { assertThat(result.failed()).isTrue(); assertThat(result.cause()).isInstanceOf(PreBidException.class); assertThat(result.cause().getMessage()).isEqualTo("Module cache 'moduleCode' can not be blank"); + + verifyNoInteractions(metrics); } @Test @@ -222,6 +325,10 @@ public void storeEntryShouldCreateCallWithApiKeyInHeader() { // then final MultiMap result = captureStoreRequestHeaders(); assertThat(result.get(HttpUtil.X_PBC_API_KEY_HEADER)).isEqualTo("pbc-api-key"); + + verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); + verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); } @Test @@ -234,6 +341,8 @@ public void retrieveModuleEntryShouldReturnFailedFutureIfKeyIsMissed() { assertThat(result.failed()).isTrue(); assertThat(result.cause()).isInstanceOf(PreBidException.class); assertThat(result.cause().getMessage()).isEqualTo("Module cache 'key' can not be blank"); + + verifyNoInteractions(metrics); } @Test @@ -246,6 +355,8 @@ public void retrieveModuleEntryShouldReturnFailedFutureIfApplicationIsMissed() { assertThat(result.failed()).isTrue(); assertThat(result.cause()).isInstanceOf(PreBidException.class); assertThat(result.cause().getMessage()).isEqualTo("Module cache 'application' can not be blank"); + + verifyNoInteractions(metrics); } @Test @@ -258,6 +369,8 @@ public void retrieveModuleEntryShouldReturnFailedFutureIfCodeIsMissed() { assertThat(result.failed()).isTrue(); assertThat(result.cause()).isInstanceOf(PreBidException.class); assertThat(result.cause().getMessage()).isEqualTo("Module cache 'moduleCode' can not be blank"); + + verifyNoInteractions(metrics); } @Test @@ -268,6 +381,8 @@ public void retrieveEntryShouldCreateCallWithApiKeyInHeader() { // then final MultiMap result = captureRetrieveRequestHeaders(); assertThat(result.get(HttpUtil.X_PBC_API_KEY_HEADER)).isEqualTo("pbc-api-key"); + + verify(metrics).updateModuleStorageCacheReadRequestTime(anyLong(), eq(MetricName.ok)); } @Test @@ -279,6 +394,8 @@ public void retrieveEntryShouldCreateCallWithKeyInParams() { final String result = captureRetrieveUrl(); assertThat(result) .isEqualTo("http://cache-service/cache?k=module.some-module-code.some-key&a=some-app"); + + verify(metrics).updateModuleStorageCacheReadRequestTime(anyLong(), eq(MetricName.ok)); } @Test @@ -290,6 +407,24 @@ public void retrieveEntryShouldReturnExpectedResponse() { // then assertThat(result.result()) .isEqualTo(ModuleCacheResponse.of("some-key", StorageDataType.JSON, "some-value")); + + verify(metrics).updateModuleStorageCacheReadRequestTime(anyLong(), eq(MetricName.ok)); + } + + @Test + public void retrieveEntryShouldReturnFailureResponse() { + //given + given(httpClient.get(anyString(), any(), anyLong())).willReturn(Future.succeededFuture( + HttpClientResponse.of(500, null, "error"))); + + // when + final Future result = + target.retrieveEntry("some-key", "some-module-code", "some-app"); + + // then + assertThat(result.succeeded()).isFalse(); + + verify(metrics).updateModuleStorageCacheReadRequestTime(anyLong(), eq(MetricName.err)); } @SneakyThrows diff --git a/src/test/java/org/prebid/server/metric/MetricsTest.java b/src/test/java/org/prebid/server/metric/MetricsTest.java index f4ea9ff2a5e..9085397d840 100644 --- a/src/test/java/org/prebid/server/metric/MetricsTest.java +++ b/src/test/java/org/prebid/server/metric/MetricsTest.java @@ -1265,6 +1265,28 @@ public void shouldIncrementVtrackWritePrebidCacheRequestTimer() { assertThat(metricRegistry.timer("account.accountId.prebid_cache.vtrack.write.err").getCount()).isOne(); } + @Test + public void shouldIncrementModuleStorageReadPrebidCacheRequestTimer() { + // when + metrics.updateModuleStorageCacheReadRequestTime(1424L, MetricName.ok); + metrics.updateModuleStorageCacheReadRequestTime(1424L, MetricName.err); + + // then + assertThat(metricRegistry.timer("prebid_cache.module_storage.read.ok").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("prebid_cache.module_storage.read.err").getCount()).isEqualTo(1); + } + + @Test + public void shouldIncrementModuleStorageWritePrebidCacheRequestTimer() { + // when + metrics.updateModuleStorageCacheWriteRequestTime(1424L, MetricName.ok); + metrics.updateModuleStorageCacheWriteRequestTime(1424L, MetricName.err); + + // then + assertThat(metricRegistry.timer("prebid_cache.module_storage.write.ok").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("prebid_cache.module_storage.write.err").getCount()).isEqualTo(1); + } + @Test public void shouldIncrementPrebidCacheCreativeSizeHistogram() { // when @@ -1303,6 +1325,22 @@ public void shouldIncrementPrebidCacheVtrackCreativeSizeHistogram() { .isEqualTo(1); } + @Test + public void shouldIncrementPrebidCacheModuleStorageCreativeSizeHistogram() { + // when + metrics.updateModuleStorageCacheCreativeSize(123, MetricName.json); + metrics.updateModuleStorageCacheCreativeSize(456, MetricName.xml); + metrics.updateModuleStorageCacheCreativeSize(789, MetricName.unknown); + metrics.updateModuleStorageCacheCreativeSize(1011, MetricName.text); + + // then + assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_size.json").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_size.xml").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_size.unknown").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_size.text").getCount()).isEqualTo(1); + } + @Test public void shouldIncrementPrebidCacheCreativeTtlHistogram() { // when @@ -1341,6 +1379,22 @@ public void shouldIncrementPrebidCacheVtrackCreativeTtlHistogram() { .isEqualTo(1); } + @Test + public void shouldIncrementPrebidCacheModuleStorageCreativeTtlHistogram() { + // when + metrics.updateModuleStorageCacheCreativeTtl(123, MetricName.json); + metrics.updateModuleStorageCacheCreativeTtl(456, MetricName.xml); + metrics.updateModuleStorageCacheCreativeTtl(789, MetricName.unknown); + metrics.updateModuleStorageCacheCreativeTtl(1011, MetricName.text); + + // then + assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_ttl.json").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_ttl.xml").getCount()).isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_ttl.unknown").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_ttl.text").getCount()).isEqualTo(1); + } + @Test public void shouldCreateCurrencyRatesGaugeMetric() { // when From 75d85b2f54ef67d60ada57585d44949574dc3c1e Mon Sep 17 00:00:00 2001 From: antonbabak Date: Mon, 25 Aug 2025 11:41:43 +0200 Subject: [PATCH 05/10] Per Module Metrics --- .../optable/targeting/v1/core/Cache.java | 4 +- .../server/cache/BasicPbcStorageService.java | 38 +++++++----- .../metric/CacheCreativeSizeMetrics.java | 13 ++-- .../metric/CacheCreativeTtlMetrics.java | 13 ++-- .../prebid/server/metric/CacheMetrics.java | 30 ++++++--- .../metric/CacheModuleStorageMetrics.java | 33 +++++----- .../server/metric/CacheVtrackMetrics.java | 7 ++- .../org/prebid/server/metric/Metrics.java | 16 ++--- .../metric/model/CacheCreativeType.java | 17 +++++ .../cache/BasicPbcStorageServiceTest.java | 62 +++++++++---------- .../org/prebid/server/metric/MetricsTest.java | 54 +++++++++------- 11 files changed, 172 insertions(+), 115 deletions(-) create mode 100644 src/main/java/org/prebid/server/metric/model/CacheCreativeType.java diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/Cache.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/Cache.java index 6aab8698a4a..97fdddf4a89 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/Cache.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/Cache.java @@ -11,8 +11,8 @@ public class Cache { - private static final String APP_CODE = "prebid-Java"; - private static final String APPLICATION = "optable-targeting"; + private static final String APPLICATION = "prebid-Java"; + private static final String APP_CODE = "optable-targeting"; private final PbcStorageService cacheService; private final JacksonMapper mapper; diff --git a/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java b/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java index b38e8fc0f1c..00bebeac278 100644 --- a/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java +++ b/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java @@ -73,7 +73,7 @@ public Future storeEntry(String key, application, ttlseconds); - updateCreativeMetrics(valueToStore, type, ttlseconds); + updateCreativeMetrics(valueToStore, type, ttlseconds, appCode); final long startTime = clock.millis(); return httpClient.post( @@ -84,7 +84,8 @@ public Future storeEntry(String key, .compose(response -> processStoreResponse( response.getStatusCode(), response.getBody(), - startTime)); + startTime, + appCode)); } @@ -115,16 +116,16 @@ private static void validateStoreData(String key, } } - private void updateCreativeMetrics(String value, StorageDataType type, Integer ttlseconds) { + private void updateCreativeMetrics(String value, StorageDataType type, Integer ttlseconds, String appCode) { final MetricName metricName = switch (type) { case XML -> MetricName.xml; case JSON -> MetricName.json; case TEXT -> MetricName.text; }; - metrics.updateModuleStorageCacheCreativeSize(value.length(), metricName); + metrics.updateModuleStorageCacheEntrySize(appCode, value.length(), metricName); if (ttlseconds != null) { - metrics.updateModuleStorageCacheCreativeTtl(ttlseconds, metricName); + metrics.updateModuleStorageCacheEntryTtl(appCode, ttlseconds, metricName); } } @@ -143,22 +144,19 @@ private String constructEntryKey(String key, String moduleCode) { return MODULE_KEY_PREFIX + MODULE_KEY_DELIMETER + moduleCode + MODULE_KEY_DELIMETER + key; } - private Future processStoreResponse(int statusCode, String responseBody, long startTime) { + private Future processStoreResponse(int statusCode, String responseBody, long startTime, String appCode) { if (statusCode != 204) { - metrics.updateModuleStorageCacheWriteRequestTime(clock.millis() - startTime, MetricName.err); + metrics.updateModuleStorageCacheWriteRequestTime(appCode, clock.millis() - startTime, MetricName.err); throw new PreBidException("HTTP status code: '%s', body: '%s' " .formatted(statusCode, responseBody)); } - metrics.updateModuleStorageCacheWriteRequestTime(clock.millis() - startTime, MetricName.ok); + metrics.updateModuleStorageCacheWriteRequestTime(appCode, clock.millis() - startTime, MetricName.ok); return Future.succeededFuture(); } @Override - public Future retrieveEntry(String key, - String appCode, - String application) { - + public Future retrieveEntry(String key, String appCode, String application) { try { validateRetrieveData(key, application, appCode); } catch (PreBidException e) { @@ -170,7 +168,11 @@ public Future retrieveEntry(String key, getRetrieveEndpoint(key, appCode, application), securedCallHeaders(), callTimeoutMs) - .map(response -> toModuleCacheResponse(response.getStatusCode(), response.getBody(), startTime)); + .map(response -> toModuleCacheResponse( + response.getStatusCode(), + response.getBody(), + startTime, + appCode)); } @@ -197,13 +199,17 @@ private String getRetrieveEndpoint(String key, + "&a=" + application; } - private ModuleCacheResponse toModuleCacheResponse(int statusCode, String responseBody, long startTime) { + private ModuleCacheResponse toModuleCacheResponse(int statusCode, + String responseBody, + long startTime, + String application) { + if (statusCode != 200) { - metrics.updateModuleStorageCacheReadRequestTime(clock.millis() - startTime, MetricName.err); + metrics.updateModuleStorageCacheReadRequestTime(application, clock.millis() - startTime, MetricName.err); throw new PreBidException("HTTP status code " + statusCode); } - metrics.updateModuleStorageCacheReadRequestTime(clock.millis() - startTime, MetricName.ok); + metrics.updateModuleStorageCacheReadRequestTime(application, clock.millis() - startTime, MetricName.ok); final ModuleCacheResponse moduleCacheResponse; try { diff --git a/src/main/java/org/prebid/server/metric/CacheCreativeSizeMetrics.java b/src/main/java/org/prebid/server/metric/CacheCreativeSizeMetrics.java index 5791b370740..f0d56074944 100644 --- a/src/main/java/org/prebid/server/metric/CacheCreativeSizeMetrics.java +++ b/src/main/java/org/prebid/server/metric/CacheCreativeSizeMetrics.java @@ -1,18 +1,23 @@ package org.prebid.server.metric; import com.codahale.metrics.MetricRegistry; +import org.prebid.server.metric.model.CacheCreativeType; import java.util.Objects; import java.util.function.Function; public class CacheCreativeSizeMetrics extends UpdatableMetrics { - CacheCreativeSizeMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { + CacheCreativeSizeMetrics(MetricRegistry metricRegistry, + CounterType counterType, + String prefix, + CacheCreativeType type) { + super(Objects.requireNonNull(metricRegistry), Objects.requireNonNull(counterType), - nameCreator(Objects.requireNonNull(prefix))); + nameCreator(Objects.requireNonNull(prefix), Objects.requireNonNull(type))); } - private static Function nameCreator(String prefix) { - return metricName -> "%s.creative_size.%s".formatted(prefix, metricName); + private static Function nameCreator(String prefix, CacheCreativeType type) { + return metricName -> "%s.%s_size.%s".formatted(prefix, type.getType(), metricName); } } diff --git a/src/main/java/org/prebid/server/metric/CacheCreativeTtlMetrics.java b/src/main/java/org/prebid/server/metric/CacheCreativeTtlMetrics.java index f5325c382aa..f79ce39f96a 100644 --- a/src/main/java/org/prebid/server/metric/CacheCreativeTtlMetrics.java +++ b/src/main/java/org/prebid/server/metric/CacheCreativeTtlMetrics.java @@ -1,19 +1,24 @@ package org.prebid.server.metric; import com.codahale.metrics.MetricRegistry; +import org.prebid.server.metric.model.CacheCreativeType; import java.util.Objects; import java.util.function.Function; public class CacheCreativeTtlMetrics extends UpdatableMetrics { - CacheCreativeTtlMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { + CacheCreativeTtlMetrics(MetricRegistry metricRegistry, + CounterType counterType, + String prefix, + CacheCreativeType type) { + super(Objects.requireNonNull(metricRegistry), Objects.requireNonNull(counterType), - nameCreator(Objects.requireNonNull(prefix))); + nameCreator(Objects.requireNonNull(prefix), Objects.requireNonNull(type))); } - private static Function nameCreator(String prefix) { - return metricName -> "%s.creative_ttl.%s".formatted(prefix, metricName); + private static Function nameCreator(String prefix, CacheCreativeType type) { + return metricName -> "%s.%s_ttl.%s".formatted(prefix, type.getType(), metricName); } } diff --git a/src/main/java/org/prebid/server/metric/CacheMetrics.java b/src/main/java/org/prebid/server/metric/CacheMetrics.java index cb1b6326fd6..6719bc995d2 100644 --- a/src/main/java/org/prebid/server/metric/CacheMetrics.java +++ b/src/main/java/org/prebid/server/metric/CacheMetrics.java @@ -1,7 +1,10 @@ package org.prebid.server.metric; import com.codahale.metrics.MetricRegistry; +import org.prebid.server.metric.model.CacheCreativeType; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.function.Function; @@ -14,7 +17,8 @@ class CacheMetrics extends UpdatableMetrics { private final CacheCreativeSizeMetrics cacheCreativeSizeMetrics; private final CacheCreativeTtlMetrics cacheCreativeTtlMetrics; private final CacheVtrackMetrics cacheVtrackMetrics; - private final CacheModuleStorageMetrics cacheModuleStorageMetrics; + private final Map cacheModuleStorageMetrics; + private final Function cacheModuleStorageMetricsCreator; CacheMetrics(MetricRegistry metricRegistry, CounterType counterType) { super( @@ -23,10 +27,14 @@ class CacheMetrics extends UpdatableMetrics { nameCreator(createPrefix())); requestsMetrics = new RequestMetrics(metricRegistry, counterType, createPrefix()); - cacheCreativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix()); - cacheCreativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix()); + cacheCreativeSizeMetrics = new CacheCreativeSizeMetrics( + metricRegistry, counterType, createPrefix(), CacheCreativeType.CREATIVE); + cacheCreativeTtlMetrics = new CacheCreativeTtlMetrics( + metricRegistry, counterType, createPrefix(), CacheCreativeType.CREATIVE); cacheVtrackMetrics = new CacheVtrackMetrics(metricRegistry, counterType, createPrefix()); - cacheModuleStorageMetrics = new CacheModuleStorageMetrics(metricRegistry, counterType, createPrefix()); + cacheModuleStorageMetrics = new HashMap<>(); + cacheModuleStorageMetricsCreator = moduleCode -> + new CacheModuleStorageMetrics(metricRegistry, counterType, createPrefix(), moduleCode); } CacheMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { @@ -36,10 +44,14 @@ class CacheMetrics extends UpdatableMetrics { nameCreator(createPrefix(Objects.requireNonNull(prefix)))); requestsMetrics = new RequestMetrics(metricRegistry, counterType, createPrefix(prefix)); - cacheCreativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix(prefix)); - cacheCreativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix(prefix)); + cacheCreativeSizeMetrics = new CacheCreativeSizeMetrics( + metricRegistry, counterType, createPrefix(prefix), CacheCreativeType.CREATIVE); + cacheCreativeTtlMetrics = new CacheCreativeTtlMetrics( + metricRegistry, counterType, createPrefix(prefix), CacheCreativeType.CREATIVE); cacheVtrackMetrics = new CacheVtrackMetrics(metricRegistry, counterType, createPrefix(prefix)); - cacheModuleStorageMetrics = new CacheModuleStorageMetrics(metricRegistry, counterType, createPrefix(prefix)); + cacheModuleStorageMetrics = new HashMap<>(); + cacheModuleStorageMetricsCreator = moduleCode -> + new CacheModuleStorageMetrics(metricRegistry, counterType, createPrefix(), moduleCode); } private static String createPrefix(String prefix) { @@ -70,7 +82,7 @@ CacheVtrackMetrics vtrack() { return cacheVtrackMetrics; } - CacheModuleStorageMetrics moduleStorage() { - return cacheModuleStorageMetrics; + CacheModuleStorageMetrics moduleStorage(String moduleCode) { + return cacheModuleStorageMetrics.computeIfAbsent(moduleCode, cacheModuleStorageMetricsCreator); } } diff --git a/src/main/java/org/prebid/server/metric/CacheModuleStorageMetrics.java b/src/main/java/org/prebid/server/metric/CacheModuleStorageMetrics.java index ff93c5be3ec..5bc52b503cb 100644 --- a/src/main/java/org/prebid/server/metric/CacheModuleStorageMetrics.java +++ b/src/main/java/org/prebid/server/metric/CacheModuleStorageMetrics.java @@ -1,6 +1,7 @@ package org.prebid.server.metric; import com.codahale.metrics.MetricRegistry; +import org.prebid.server.metric.model.CacheCreativeType; import java.util.Objects; import java.util.function.Function; @@ -9,27 +10,29 @@ class CacheModuleStorageMetrics extends UpdatableMetrics { private final CacheReadMetrics readMetrics; private final CacheWriteMetrics writeMetrics; - private final CacheCreativeSizeMetrics creativeSizeMetrics; - private final CacheCreativeTtlMetrics creativeTtlMetrics; + private final CacheCreativeSizeMetrics entrySizeMetrics; + private final CacheCreativeTtlMetrics entryTtlMetrics; - CacheModuleStorageMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { + CacheModuleStorageMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix, String module) { super( Objects.requireNonNull(metricRegistry), Objects.requireNonNull(counterType), - nameCreator(createPrefix(Objects.requireNonNull(prefix)))); - - readMetrics = new CacheReadMetrics(metricRegistry, counterType, createPrefix(prefix)); - writeMetrics = new CacheWriteMetrics(metricRegistry, counterType, createPrefix(prefix)); - creativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix(prefix)); - creativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix(prefix)); + nameCreator(createPrefix(Objects.requireNonNull(prefix), Objects.requireNonNull(module)))); + + readMetrics = new CacheReadMetrics(metricRegistry, counterType, createPrefix(prefix, module)); + writeMetrics = new CacheWriteMetrics(metricRegistry, counterType, createPrefix(prefix, module)); + entrySizeMetrics = new CacheCreativeSizeMetrics( + metricRegistry, counterType, createPrefix(prefix, module), CacheCreativeType.ENTRY); + entryTtlMetrics = new CacheCreativeTtlMetrics( + metricRegistry, counterType, createPrefix(prefix, module), CacheCreativeType.ENTRY); } private static Function nameCreator(String prefix) { return metricName -> "%s.%s".formatted(prefix, metricName); } - private static String createPrefix(String prefix) { - return prefix + ".module_storage"; + private static String createPrefix(String prefix, String moduleCode) { + return "%s.module_storage.%s".formatted(prefix, moduleCode); } CacheReadMetrics read() { @@ -40,12 +43,12 @@ CacheWriteMetrics write() { return writeMetrics; } - CacheCreativeSizeMetrics creativeSize() { - return creativeSizeMetrics; + CacheCreativeSizeMetrics entrySize() { + return entrySizeMetrics; } - CacheCreativeTtlMetrics creativeTtl() { - return creativeTtlMetrics; + CacheCreativeTtlMetrics entryTtl() { + return entryTtlMetrics; } } diff --git a/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java b/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java index ba1644fa136..e3a44639297 100644 --- a/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java +++ b/src/main/java/org/prebid/server/metric/CacheVtrackMetrics.java @@ -1,6 +1,7 @@ package org.prebid.server.metric; import com.codahale.metrics.MetricRegistry; +import org.prebid.server.metric.model.CacheCreativeType; import java.util.Objects; import java.util.function.Function; @@ -20,8 +21,10 @@ class CacheVtrackMetrics extends UpdatableMetrics { readMetrics = new CacheReadMetrics(metricRegistry, counterType, createPrefix(prefix)); writeMetrics = new CacheWriteMetrics(metricRegistry, counterType, createPrefix(prefix)); - creativeSizeMetrics = new CacheCreativeSizeMetrics(metricRegistry, counterType, createPrefix(prefix)); - creativeTtlMetrics = new CacheCreativeTtlMetrics(metricRegistry, counterType, createPrefix(prefix)); + creativeSizeMetrics = new CacheCreativeSizeMetrics( + metricRegistry, counterType, createPrefix(prefix), CacheCreativeType.CREATIVE); + creativeTtlMetrics = new CacheCreativeTtlMetrics( + metricRegistry, counterType, createPrefix(prefix), CacheCreativeType.CREATIVE); } private static Function nameCreator(String prefix) { diff --git a/src/main/java/org/prebid/server/metric/Metrics.java b/src/main/java/org/prebid/server/metric/Metrics.java index 0ec95d1b962..bc19ab3f91c 100644 --- a/src/main/java/org/prebid/server/metric/Metrics.java +++ b/src/main/java/org/prebid/server/metric/Metrics.java @@ -629,20 +629,20 @@ public void updateVtrackCacheCreativeTtl(String accountId, Integer creativeTtl, forAccount(accountId).cache().vtrack().creativeTtl().updateHistogram(creativeType, creativeTtl); } - public void updateModuleStorageCacheReadRequestTime(long timeElapsed, MetricName metricName) { - cache().moduleStorage().read().updateTimer(metricName, timeElapsed); + public void updateModuleStorageCacheReadRequestTime(String moduleCode, long timeElapsed, MetricName metricName) { + cache().moduleStorage(moduleCode).read().updateTimer(metricName, timeElapsed); } - public void updateModuleStorageCacheWriteRequestTime(long timeElapsed, MetricName metricName) { - cache().moduleStorage().write().updateTimer(metricName, timeElapsed); + public void updateModuleStorageCacheWriteRequestTime(String moduleCode, long timeElapsed, MetricName metricName) { + cache().moduleStorage(moduleCode).write().updateTimer(metricName, timeElapsed); } - public void updateModuleStorageCacheCreativeSize(int creativeSize, MetricName creativeType) { - cache().moduleStorage().creativeSize().updateHistogram(creativeType, creativeSize); + public void updateModuleStorageCacheEntrySize(String moduleCode, int entrySize, MetricName type) { + cache().moduleStorage(moduleCode).entrySize().updateHistogram(type, entrySize); } - public void updateModuleStorageCacheCreativeTtl(Integer creativeTtl, MetricName creativeType) { - cache().moduleStorage().creativeTtl().updateHistogram(creativeType, creativeTtl); + public void updateModuleStorageCacheEntryTtl(String moduleCode, Integer entryTtl, MetricName type) { + cache().moduleStorage(moduleCode).entryTtl().updateHistogram(type, entryTtl); } public void updateAuctionCacheRequestTime(String accountId, long timeElapsed, MetricName metricName) { diff --git a/src/main/java/org/prebid/server/metric/model/CacheCreativeType.java b/src/main/java/org/prebid/server/metric/model/CacheCreativeType.java new file mode 100644 index 00000000000..cd7b8ec78e2 --- /dev/null +++ b/src/main/java/org/prebid/server/metric/model/CacheCreativeType.java @@ -0,0 +1,17 @@ +package org.prebid.server.metric.model; + +public enum CacheCreativeType { + + ENTRY("entry"), + CREATIVE("creative"); + + private final String type; + + CacheCreativeType(String type) { + this.type = type; + } + + public String getType() { + return this.type; + } +} diff --git a/src/test/java/org/prebid/server/cache/BasicPbcStorageServiceTest.java b/src/test/java/org/prebid/server/cache/BasicPbcStorageServiceTest.java index da8b0c500ad..a995429ba79 100644 --- a/src/test/java/org/prebid/server/cache/BasicPbcStorageServiceTest.java +++ b/src/test/java/org/prebid/server/cache/BasicPbcStorageServiceTest.java @@ -84,9 +84,9 @@ public void storeModuleEntryShouldReturnFailureResponse() { final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getKey()).isEqualTo("module.some-module-code.some-key"); - verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); - verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); - verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.err)); + verify(metrics).updateModuleStorageCacheEntryTtl("some-module-code", 12, MetricName.text); + verify(metrics).updateModuleStorageCacheEntrySize("some-module-code", 16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.err)); } @Test @@ -103,9 +103,9 @@ public void storeModuleEntryShouldStoreExpectedKey() { final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getKey()).isEqualTo("module.some-module-code.some-key"); - verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); - verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); - verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheEntryTtl("some-module-code", 12, MetricName.text); + verify(metrics).updateModuleStorageCacheEntrySize("some-module-code", 16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -122,9 +122,9 @@ public void storeModuleEntryShouldStoreExpectedValue() { final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getValue()).isEqualTo("c29tZS12YWx1ZQ=="); - verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); - verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); - verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheEntryTtl("some-module-code", 12, MetricName.text); + verify(metrics).updateModuleStorageCacheEntrySize("some-module-code", 16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -141,9 +141,9 @@ public void storeModuleEntryShouldStoreExpectedApplication() { final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getApplication()).isEqualTo("some-application"); - verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); - verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); - verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheEntryTtl("some-module-code", 12, MetricName.text); + verify(metrics).updateModuleStorageCacheEntrySize("some-module-code", 16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -160,9 +160,9 @@ public void storeModuleEntryShouldStoreTextMediaType() { final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getType()).isEqualTo(StorageDataType.TEXT); - verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); - verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); - verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheEntryTtl("some-module-code", 12, MetricName.text); + verify(metrics).updateModuleStorageCacheEntrySize("some-module-code", 16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -179,9 +179,9 @@ public void storeModuleEntryShouldStoreXmlMediaType() { final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getType()).isEqualTo(StorageDataType.XML); - verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.xml); - verify(metrics).updateModuleStorageCacheCreativeSize(13, MetricName.xml); - verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheEntryTtl("some-module-code", 12, MetricName.xml); + verify(metrics).updateModuleStorageCacheEntrySize("some-module-code", 13, MetricName.xml); + verify(metrics).updateModuleStorageCacheWriteRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -198,9 +198,9 @@ public void storeModuleEntryShouldStoreJsonMediaType() { final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getType()).isEqualTo(StorageDataType.JSON); - verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.json); - verify(metrics).updateModuleStorageCacheCreativeSize(2, MetricName.json); - verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheEntryTtl("some-module-code", 12, MetricName.json); + verify(metrics).updateModuleStorageCacheEntrySize("some-module-code", 2, MetricName.json); + verify(metrics).updateModuleStorageCacheWriteRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -217,9 +217,9 @@ public void storeModuleEntryShouldStoreExpectedTtl() { final ModuleCacheRequest result = captureModuleCacheRequest(); assertThat(result.getTtlseconds()).isEqualTo(12); - verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); - verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); - verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheEntryTtl("some-module-code", 12, MetricName.text); + verify(metrics).updateModuleStorageCacheEntrySize("some-module-code", 16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -326,9 +326,9 @@ public void storeEntryShouldCreateCallWithApiKeyInHeader() { final MultiMap result = captureStoreRequestHeaders(); assertThat(result.get(HttpUtil.X_PBC_API_KEY_HEADER)).isEqualTo("pbc-api-key"); - verify(metrics).updateModuleStorageCacheCreativeTtl(12, MetricName.text); - verify(metrics).updateModuleStorageCacheCreativeSize(16, MetricName.text); - verify(metrics).updateModuleStorageCacheWriteRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheEntryTtl("some-module-code", 12, MetricName.text); + verify(metrics).updateModuleStorageCacheEntrySize("some-module-code", 16, MetricName.text); + verify(metrics).updateModuleStorageCacheWriteRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -382,7 +382,7 @@ public void retrieveEntryShouldCreateCallWithApiKeyInHeader() { final MultiMap result = captureRetrieveRequestHeaders(); assertThat(result.get(HttpUtil.X_PBC_API_KEY_HEADER)).isEqualTo("pbc-api-key"); - verify(metrics).updateModuleStorageCacheReadRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheReadRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -395,7 +395,7 @@ public void retrieveEntryShouldCreateCallWithKeyInParams() { assertThat(result) .isEqualTo("http://cache-service/cache?k=module.some-module-code.some-key&a=some-app"); - verify(metrics).updateModuleStorageCacheReadRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheReadRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -408,7 +408,7 @@ public void retrieveEntryShouldReturnExpectedResponse() { assertThat(result.result()) .isEqualTo(ModuleCacheResponse.of("some-key", StorageDataType.JSON, "some-value")); - verify(metrics).updateModuleStorageCacheReadRequestTime(anyLong(), eq(MetricName.ok)); + verify(metrics).updateModuleStorageCacheReadRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.ok)); } @Test @@ -424,7 +424,7 @@ public void retrieveEntryShouldReturnFailureResponse() { // then assertThat(result.succeeded()).isFalse(); - verify(metrics).updateModuleStorageCacheReadRequestTime(anyLong(), eq(MetricName.err)); + verify(metrics).updateModuleStorageCacheReadRequestTime(eq("some-module-code"), anyLong(), eq(MetricName.err)); } @SneakyThrows diff --git a/src/test/java/org/prebid/server/metric/MetricsTest.java b/src/test/java/org/prebid/server/metric/MetricsTest.java index 9085397d840..8854e5af3c4 100644 --- a/src/test/java/org/prebid/server/metric/MetricsTest.java +++ b/src/test/java/org/prebid/server/metric/MetricsTest.java @@ -1268,23 +1268,23 @@ public void shouldIncrementVtrackWritePrebidCacheRequestTimer() { @Test public void shouldIncrementModuleStorageReadPrebidCacheRequestTimer() { // when - metrics.updateModuleStorageCacheReadRequestTime(1424L, MetricName.ok); - metrics.updateModuleStorageCacheReadRequestTime(1424L, MetricName.err); + metrics.updateModuleStorageCacheReadRequestTime("module_code", 1424L, MetricName.ok); + metrics.updateModuleStorageCacheReadRequestTime("module_code", 1424L, MetricName.err); // then - assertThat(metricRegistry.timer("prebid_cache.module_storage.read.ok").getCount()).isEqualTo(1); - assertThat(metricRegistry.timer("prebid_cache.module_storage.read.err").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("prebid_cache.module_storage.module_code.read.ok").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("prebid_cache.module_storage.module_code.read.err").getCount()).isEqualTo(1); } @Test public void shouldIncrementModuleStorageWritePrebidCacheRequestTimer() { // when - metrics.updateModuleStorageCacheWriteRequestTime(1424L, MetricName.ok); - metrics.updateModuleStorageCacheWriteRequestTime(1424L, MetricName.err); + metrics.updateModuleStorageCacheWriteRequestTime("module_code", 1424L, MetricName.ok); + metrics.updateModuleStorageCacheWriteRequestTime("module_code", 1424L, MetricName.err); // then - assertThat(metricRegistry.timer("prebid_cache.module_storage.write.ok").getCount()).isEqualTo(1); - assertThat(metricRegistry.timer("prebid_cache.module_storage.write.err").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("prebid_cache.module_storage.module_code.write.ok").getCount()).isEqualTo(1); + assertThat(metricRegistry.timer("prebid_cache.module_storage.module_code.write.err").getCount()).isEqualTo(1); } @Test @@ -1328,17 +1328,20 @@ public void shouldIncrementPrebidCacheVtrackCreativeSizeHistogram() { @Test public void shouldIncrementPrebidCacheModuleStorageCreativeSizeHistogram() { // when - metrics.updateModuleStorageCacheCreativeSize(123, MetricName.json); - metrics.updateModuleStorageCacheCreativeSize(456, MetricName.xml); - metrics.updateModuleStorageCacheCreativeSize(789, MetricName.unknown); - metrics.updateModuleStorageCacheCreativeSize(1011, MetricName.text); + metrics.updateModuleStorageCacheEntrySize("module_code", 123, MetricName.json); + metrics.updateModuleStorageCacheEntrySize("module_code", 456, MetricName.xml); + metrics.updateModuleStorageCacheEntrySize("module_code", 789, MetricName.unknown); + metrics.updateModuleStorageCacheEntrySize("module_code", 1011, MetricName.text); // then - assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_size.json").getCount()).isEqualTo(1); - assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_size.xml").getCount()).isEqualTo(1); - assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_size.unknown").getCount()) + assertThat(metricRegistry.histogram("prebid_cache.module_storage.module_code.entry_size.json").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.module_code.entry_size.xml").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.module_code.entry_size.unknown").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.module_code.entry_size.text").getCount()) .isEqualTo(1); - assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_size.text").getCount()).isEqualTo(1); } @Test @@ -1382,17 +1385,20 @@ public void shouldIncrementPrebidCacheVtrackCreativeTtlHistogram() { @Test public void shouldIncrementPrebidCacheModuleStorageCreativeTtlHistogram() { // when - metrics.updateModuleStorageCacheCreativeTtl(123, MetricName.json); - metrics.updateModuleStorageCacheCreativeTtl(456, MetricName.xml); - metrics.updateModuleStorageCacheCreativeTtl(789, MetricName.unknown); - metrics.updateModuleStorageCacheCreativeTtl(1011, MetricName.text); + metrics.updateModuleStorageCacheEntryTtl("module_code", 123, MetricName.json); + metrics.updateModuleStorageCacheEntryTtl("module_code", 456, MetricName.xml); + metrics.updateModuleStorageCacheEntryTtl("module_code", 789, MetricName.unknown); + metrics.updateModuleStorageCacheEntryTtl("module_code", 1011, MetricName.text); // then - assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_ttl.json").getCount()).isEqualTo(1); - assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_ttl.xml").getCount()).isEqualTo(1); - assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_ttl.unknown").getCount()) + assertThat(metricRegistry.histogram("prebid_cache.module_storage.module_code.entry_ttl.json").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.module_code.entry_ttl.xml").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.module_code.entry_ttl.unknown").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.histogram("prebid_cache.module_storage.module_code.entry_ttl.text").getCount()) .isEqualTo(1); - assertThat(metricRegistry.histogram("prebid_cache.module_storage.creative_ttl.text").getCount()).isEqualTo(1); } @Test From cdbf4c714a0453774dca8455aad052080eb0474e Mon Sep 17 00:00:00 2001 From: antonbabak Date: Mon, 20 Oct 2025 09:48:10 +0200 Subject: [PATCH 06/10] Fix comments --- .../java/org/prebid/server/cache/BasicPbcStorageService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java b/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java index 00bebeac278..a665f00c845 100644 --- a/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java +++ b/src/main/java/org/prebid/server/cache/BasicPbcStorageService.java @@ -47,7 +47,7 @@ public BasicPbcStorageService(HttpClient httpClient, this.callTimeoutMs = callTimeoutMs; this.mapper = Objects.requireNonNull(mapper); this.clock = Objects.requireNonNull(clock); - this.metrics = metrics; + this.metrics = Objects.requireNonNull(metrics); } @Override From 560231bf956a3c4a53054dc59e86c98f42c8a077 Mon Sep 17 00:00:00 2001 From: osulzhenko <125548596+osulzhenko@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:46:55 +0300 Subject: [PATCH 07/10] Tests: Module Storage Metrics (#4219) --- .../server/functional/model/ModuleName.groovy | 1 + .../functional/model/config/Audience.groovy | 10 + .../functional/model/config/AudienceId.groovy | 9 + .../model/config/CacheProperties.groovy | 21 ++ .../model/config/ExecutionGroup.groovy | 2 +- .../model/config/IdentifierType.groovy | 49 +++++ .../config/ModuleHookImplementation.groovy | 1 + .../model/config/OperatingSystem.groovy | 19 ++ .../config/OptableTargetingConfig.groovy | 33 +++ .../model/config/PbsModulesConfig.groovy | 1 + .../model/config/TargetingOrtb.groovy | 10 + .../model/config/TargetingResult.groovy | 10 + .../scaffolding/StoredCache.groovy | 118 ++++++++++ .../tests/module/ModuleBaseSpec.groovy | 12 ++ .../optabletargeting/CacheStorageSpec.groovy | 201 ++++++++++++++++++ 15 files changed, 496 insertions(+), 1 deletion(-) create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/Audience.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/AudienceId.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/CacheProperties.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/IdentifierType.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/OperatingSystem.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/OptableTargetingConfig.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/TargetingOrtb.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/TargetingResult.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/StoredCache.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy diff --git a/src/test/groovy/org/prebid/server/functional/model/ModuleName.groovy b/src/test/groovy/org/prebid/server/functional/model/ModuleName.groovy index 9aee1c69c68..19a29ae0058 100644 --- a/src/test/groovy/org/prebid/server/functional/model/ModuleName.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/ModuleName.groovy @@ -8,6 +8,7 @@ enum ModuleName { PB_RESPONSE_CORRECTION ("pb-response-correction"), ORTB2_BLOCKING("ortb2-blocking"), PB_REQUEST_CORRECTION('pb-request-correction'), + OPTABLE_TARGETING('optable-targeting'), PB_RULE_ENGINE('pb-rule-engine') @JsonValue diff --git a/src/test/groovy/org/prebid/server/functional/model/config/Audience.groovy b/src/test/groovy/org/prebid/server/functional/model/config/Audience.groovy new file mode 100644 index 00000000000..9ea6345ff0e --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/Audience.groovy @@ -0,0 +1,10 @@ +package org.prebid.server.functional.model.config + +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +class Audience { + + String provider + List ids +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AudienceId.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AudienceId.groovy new file mode 100644 index 00000000000..e964b9f84b5 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/AudienceId.groovy @@ -0,0 +1,9 @@ +package org.prebid.server.functional.model.config + +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +class AudienceId { + + String id +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/CacheProperties.groovy b/src/test/groovy/org/prebid/server/functional/model/config/CacheProperties.groovy new file mode 100644 index 00000000000..028d5b203df --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/CacheProperties.groovy @@ -0,0 +1,21 @@ +package org.prebid.server.functional.model.config + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.ToString +import org.prebid.server.functional.util.PBSUtils + +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) +class CacheProperties { + + Boolean enabled + Integer ttlSeconds + + static CacheProperties getDefault() { + new CacheProperties().tap { + enabled = true + ttlSeconds = PBSUtils.randomNumber + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy b/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy index 09e95d7753d..eb32b75e729 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy @@ -18,7 +18,7 @@ class ExecutionGroup { static ExecutionGroup getModuleExecutionGroup(ModuleName name, Stage stage) { new ExecutionGroup().tap { - timeout = 100 + timeout = 1000 hookSequence = [new HookId(moduleCode: name.code, hookImplCode: ModuleHookImplementation.forValue(name, stage).code)] } } diff --git a/src/test/groovy/org/prebid/server/functional/model/config/IdentifierType.groovy b/src/test/groovy/org/prebid/server/functional/model/config/IdentifierType.groovy new file mode 100644 index 00000000000..7148c869c77 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/IdentifierType.groovy @@ -0,0 +1,49 @@ +package org.prebid.server.functional.model.config + +import com.fasterxml.jackson.annotation.JsonValue + +import static org.prebid.server.functional.model.config.OperatingSystem.ANDROID +import static org.prebid.server.functional.model.config.OperatingSystem.FIRE +import static org.prebid.server.functional.model.config.OperatingSystem.IOS +import static org.prebid.server.functional.model.config.OperatingSystem.ROKU +import static org.prebid.server.functional.model.config.OperatingSystem.TIZEN + +enum IdentifierType { + + EMAIL_ADDRESS("e"), + PHONE_NUMBER("p"), + POSTAL_CODE("z"), + APPLE_IDFA("a"), + GOOGLE_GAID("g"), + ROKU_RIDA("r"), + SAMSUNG_TIFA("s"), + AMAZON_AFAI("f"), + NET_ID("n"), + ID5("id5"), + UTIQ("utiq"), + OPTABLE_VID("v") + + @JsonValue + final String value + + IdentifierType(String value) { + this.value = value + } + + static IdentifierType fromOS(OperatingSystem os) { + switch (os) { + case IOS: + return APPLE_IDFA + case ANDROID: + return GOOGLE_GAID + case ROKU: + return ROKU_RIDA + case TIZEN: + return SAMSUNG_TIFA + case FIRE: + return AMAZON_AFAI + default: + throw new IllegalArgumentException("Unsupported OS: " + os); + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/ModuleHookImplementation.groovy b/src/test/groovy/org/prebid/server/functional/model/config/ModuleHookImplementation.groovy index 4d9424e1c39..af7bf670c95 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/ModuleHookImplementation.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/ModuleHookImplementation.groovy @@ -11,6 +11,7 @@ enum ModuleHookImplementation { ORTB2_BLOCKING_BIDDER_REQUEST("ortb2-blocking-bidder-request"), ORTB2_BLOCKING_RAW_BIDDER_RESPONSE("ortb2-blocking-raw-bidder-response"), PB_REQUEST_CORRECTION_PROCESSED_AUCTION_REQUEST("pb-request-correction-processed-auction-request"), + OPTABLE_TARGETING_PROCESSED_AUCTION_REQUEST("optable-targeting-processed-auction-request-hook"), PB_RULES_ENGINE_PROCESSED_AUCTION_REQUEST("pb-rule-engine-processed-auction-request") @JsonValue diff --git a/src/test/groovy/org/prebid/server/functional/model/config/OperatingSystem.groovy b/src/test/groovy/org/prebid/server/functional/model/config/OperatingSystem.groovy new file mode 100644 index 00000000000..f10e28a4cb6 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/OperatingSystem.groovy @@ -0,0 +1,19 @@ +package org.prebid.server.functional.model.config + +import com.fasterxml.jackson.annotation.JsonValue + +enum OperatingSystem { + + IOS("ios"), + ANDROID("android"), + ROKU("roku"), + TIZEN("tizen"), + FIRE("fire") + + @JsonValue + final String value + + OperatingSystem(String value) { + this.value = value; + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/OptableTargetingConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/OptableTargetingConfig.groovy new file mode 100644 index 00000000000..56e6f3415b8 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/OptableTargetingConfig.groovy @@ -0,0 +1,33 @@ +package org.prebid.server.functional.model.config + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.ToString +import org.prebid.server.functional.util.PBSUtils + +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) +class OptableTargetingConfig { + + String apiEndpoint + String apiKey + String tenant + String origin + Map ppidMapping + Boolean adserverTargeting + Long timeout + String idPrefixOrder + CacheProperties cache + + static OptableTargetingConfig getDefault(Map ppidMapping) { + new OptableTargetingConfig().tap { + it.apiKey = PBSUtils.randomString + it.tenant = PBSUtils.randomString + it.origin = PBSUtils.randomString + it.apiEndpoint = PBSUtils.randomString + it.adserverTargeting = true + it.ppidMapping = ppidMapping + it.cache = CacheProperties.default + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/PbsModulesConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/PbsModulesConfig.groovy index 801178fd4d4..ac2685742b6 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/PbsModulesConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/PbsModulesConfig.groovy @@ -13,5 +13,6 @@ class PbsModulesConfig { Ortb2BlockingConfig ortb2Blocking PbResponseCorrection pbResponseCorrection PbRequestCorrectionConfig pbRequestCorrection + OptableTargetingConfig optableTargeting PbRulesEngine pbRuleEngine } diff --git a/src/test/groovy/org/prebid/server/functional/model/config/TargetingOrtb.groovy b/src/test/groovy/org/prebid/server/functional/model/config/TargetingOrtb.groovy new file mode 100644 index 00000000000..0e6b8d93119 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/TargetingOrtb.groovy @@ -0,0 +1,10 @@ +package org.prebid.server.functional.model.config + +import groovy.transform.ToString +import org.prebid.server.functional.model.request.auction.User + +@ToString(includeNames = true, ignoreNulls = true) +class TargetingOrtb { + + User user +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/TargetingResult.groovy b/src/test/groovy/org/prebid/server/functional/model/config/TargetingResult.groovy new file mode 100644 index 00000000000..d9f2bc7b30d --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/TargetingResult.groovy @@ -0,0 +1,10 @@ +package org.prebid.server.functional.model.config + +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +class TargetingResult { + + List audience + TargetingOrtb ortb2 +} diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/StoredCache.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/StoredCache.groovy new file mode 100644 index 00000000000..6f772af6d70 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/StoredCache.groovy @@ -0,0 +1,118 @@ +package org.prebid.server.functional.testcontainers.scaffolding + +import org.mockserver.matchers.TimeToLive +import org.mockserver.matchers.Times +import org.mockserver.model.HttpRequest +import org.mockserver.model.HttpStatusCode +import org.prebid.server.functional.model.config.Audience +import org.prebid.server.functional.model.config.AudienceId +import org.prebid.server.functional.model.config.IdentifierType +import org.prebid.server.functional.model.config.OptableTargetingConfig +import org.prebid.server.functional.model.config.TargetingOrtb +import org.prebid.server.functional.model.config.TargetingResult +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.User +import org.prebid.server.functional.util.PBSUtils +import org.testcontainers.containers.MockServerContainer + +import java.nio.charset.StandardCharsets + +import static org.mockserver.model.HttpRequest.request +import static org.mockserver.model.HttpResponse.response +import static org.mockserver.model.HttpStatusCode.NO_CONTENT_204 +import static org.mockserver.model.HttpStatusCode.OK_200 + +class StoredCache extends NetworkScaffolding { + + private static final String CACHE_ENDPOINT = "/stored-cache" + + StoredCache(MockServerContainer mockServerContainer) { + super(mockServerContainer, CACHE_ENDPOINT) + } + + @Override + protected HttpRequest getRequest(String impId) {} + + @Override + HttpRequest getRequest() { + request().withMethod("GET") + .withPath(endpoint) + } + + @Override + void setResponse() {} + + TargetingResult setTargetingResponse(BidRequest bidRequest, OptableTargetingConfig config) { + def targetingResult = getBodyByRequest(bidRequest) + mockServerClient.when(request() + .withMethod("GET") + .withPath("$endpoint${QueryBuilder.buildQuery(bidRequest, config)}"), Times.unlimited(), TimeToLive.unlimited(), -10) + .respond { response().withStatusCode(OK_200.code()).withBody(encode(targetingResult)) } + targetingResult + } + + TargetingResult setCachedTargetingResponse(BidRequest bidRequest) { + def targetingResult = getBodyByRequest(bidRequest) + mockServerClient.when(request() + .withMethod("GET") + .withPath(endpoint), Times.unlimited(), TimeToLive.unlimited(), -10) + .respond { response().withStatusCode(OK_200.code()).withBody(encode(targetingResult)) } + targetingResult + } + + void setCachingResponse(HttpStatusCode statusCode = NO_CONTENT_204) { + mockServerClient.when(request() + .withMethod("POST") + .withPath(endpoint), Times.unlimited(), TimeToLive.unlimited(), -10) + .respond { response().withStatusCode(statusCode.code()) } + } + + private static TargetingResult getBodyByRequest(BidRequest bidRequest) { + new TargetingResult().tap { + it.audience = [new Audience(ids: [new AudienceId(id: PBSUtils.randomString)], provider: PBSUtils.randomString)] + it.ortb2 = new TargetingOrtb(user: new User(data: bidRequest.user.data, eids: bidRequest.user.eids)) + } + } + + private class QueryBuilder { + + static String buildQuery(BidRequest bidRequest, OptableTargetingConfig config) { + buildIdsString(config) + buildAttributesString(bidRequest, config) + } + + private static String buildIdsString(OptableTargetingConfig config) { + def ppids = config.ppidMapping + if (!ppids) { + return '' + } + + def reorderedIds = reorderIds(ppids.keySet(), config.idPrefixOrder) + + reorderedIds.collect { id -> + def value = ppids[id] + "&id=${URLEncoder.encode("${id.value}:${value}", StandardCharsets.UTF_8)}" + }.join('') + } + + private static Set reorderIds(Set ids, String idPrefixOrder) { + if (!idPrefixOrder) { + return ids + } + def prefixOrder = idPrefixOrder.split(',') as List + def prefixToPriority = prefixOrder.collectEntries { v, i -> [(v): i] } + ids.sort { prefixToPriority.get(it.value, Integer.MAX_VALUE) } + } + + private static String buildAttributesString(BidRequest bidRequest, OptableTargetingConfig config) { + def regs = bidRequest.regs + def gdpr = regs?.gdpr + def gdprConsent = bidRequest.user?.consent + + [gdprConsent != null ? "&gdpr_consent=${gdprConsent}" : null, + "&gdpr=${gdpr ? 1 : 0}", + regs?.gpp ? "&gpp=${regs.gpp}" : null, + regs?.gppSid ? "&gpp_sid=${regs.gppSid.first()}" : null, + config?.timeout ? "&timeout=${config.timeout}ms" : null].findAll().join('') + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/ModuleBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/ModuleBaseSpec.groovy index f721b93eac0..c0933a238e7 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/module/ModuleBaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/module/ModuleBaseSpec.groovy @@ -7,7 +7,9 @@ import org.prebid.server.functional.model.response.auction.AnalyticResult import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.model.response.auction.InvocationResult import org.prebid.server.functional.tests.BaseSpec +import org.prebid.server.functional.util.PBSUtils +import static org.prebid.server.functional.model.ModuleName.OPTABLE_TARGETING import static org.prebid.server.functional.model.ModuleName.ORTB2_BLOCKING import static org.prebid.server.functional.model.ModuleName.PB_RESPONSE_CORRECTION import static org.prebid.server.functional.model.ModuleName.PB_RICHMEDIA_FILTER @@ -16,6 +18,7 @@ import static org.prebid.server.functional.model.ModuleName.PB_RULE_ENGINE import static org.prebid.server.functional.model.config.Endpoint.OPENRTB2_AUCTION import static org.prebid.server.functional.model.config.Stage.ALL_PROCESSED_BID_RESPONSES import static org.prebid.server.functional.model.config.Stage.PROCESSED_AUCTION_REQUEST +import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer class ModuleBaseSpec extends BaseSpec { @@ -56,6 +59,15 @@ class ModuleBaseSpec extends BaseSpec { .collectEntries { key, value -> [(key.toString()): value.toString()] } } + protected static Map getOptableTargetingSettings(boolean isEnabled = true, Endpoint endpoint = OPENRTB2_AUCTION) { + ["hooks.${OPTABLE_TARGETING.code}.enabled": isEnabled as String, + "hooks.modules.${OPTABLE_TARGETING.code}.api-endpoint" : "$networkServiceContainer.rootUri/stored-cache".toString(), + "hooks.modules.${OPTABLE_TARGETING.code}.tenant" : PBSUtils.randomString, + "hooks.modules.${OPTABLE_TARGETING.code}.origin" : PBSUtils.randomString, + "hooks.host-execution-plan" : encode(ExecutionPlan.getSingleEndpointExecutionPlan(endpoint, [(PROCESSED_AUCTION_REQUEST): [OPTABLE_TARGETING]]))] + .collectEntries { key, value -> [(key.toString()): value.toString()] } + } + protected static Map getOrtb2BlockingSettings(boolean isEnabled = true) { ["hooks.${ORTB2_BLOCKING.code}.enabled": isEnabled as String] } diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy new file mode 100644 index 00000000000..3d1665a101b --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy @@ -0,0 +1,201 @@ +package org.prebid.server.functional.tests.module.optabletargeting + +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.config.AccountHooksConfiguration +import org.prebid.server.functional.model.config.IdentifierType +import org.prebid.server.functional.model.config.OperatingSystem +import org.prebid.server.functional.model.config.OptableTargetingConfig +import org.prebid.server.functional.model.config.PbsModulesConfig +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Data +import org.prebid.server.functional.model.request.auction.Device +import org.prebid.server.functional.model.request.auction.Eid +import org.prebid.server.functional.model.request.auction.Geo +import org.prebid.server.functional.model.request.auction.PublicCountryIp +import org.prebid.server.functional.model.request.auction.User +import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.testcontainers.scaffolding.StoredCache +import org.prebid.server.functional.tests.module.ModuleBaseSpec +import org.prebid.server.functional.util.PBSUtils + +import static org.apache.commons.codec.binary.Base64.encodeBase64 +import static org.mockserver.model.HttpStatusCode.NOT_FOUND_404 +import static org.prebid.server.functional.model.ModuleName.OPTABLE_TARGETING +import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer + +class CacheStorageSpec extends ModuleBaseSpec { + + private static final String METRIC_CREATIVE_SIZE_TEXT = "prebid_cache.module_storage.${OPTABLE_TARGETING.code}.entry_size.text" + private static final String METRIC_CREATIVE_TTL_TEXT = "prebid_cache.module_storage.${OPTABLE_TARGETING.code}.entry_ttl.text" + + private static final String METRIC_CREATIVE_READ_OK = "prebid_cache.module_storage.${OPTABLE_TARGETING.code}.read.ok" + private static final String METRIC_CREATIVE_READ_ERR = "prebid_cache.module_storage.${OPTABLE_TARGETING.code}.read.err" + private static final String METRIC_CREATIVE_WRITE_OK = "prebid_cache.module_storage.${OPTABLE_TARGETING.code}.write.ok" + private static final String METRIC_CREATIVE_WRITE_ERR = "prebid_cache.module_storage.${OPTABLE_TARGETING.code}.write.err" + + private static final StoredCache storedCache = new StoredCache(networkServiceContainer) + + private static final Map CACHE_STORAGE_CONFIG = ['storage.pbc.path' : "$networkServiceContainer.rootUri/stored-cache".toString(), + 'storage.pbc.call-timeout-ms': '1000', + 'storage.pbc.enabled' : 'true', + 'cache.module.enabled' : 'true', + 'pbc.api.key' : PBSUtils.randomString, + 'cache.api-key-secured' : 'false'] + private static final Map MODULE_STORAGE_CACHE_CONFIG = getOptableTargetingSettings() + CACHE_STORAGE_CONFIG + private static final PrebidServerService prebidServerStoredCacheService = pbsServiceFactory.getService(MODULE_STORAGE_CACHE_CONFIG) + + def setup() { + storedCache.reset() + } + + def cleanupSpec() { + pbsServiceFactory.removeContainer(MODULE_STORAGE_CACHE_CONFIG) + } + + def "PBS should update error metrics when no cached requests present"() { + given: "Default BidRequest with cache and device info" + def randomIfa = PBSUtils.randomString + def system = PBSUtils.getRandomEnum(OperatingSystem) + def bidRequest = getBidRequestForModuleCacheStorage(randomIfa, system) + + and: "Account with optable targeting module" + def targetingConfig = OptableTargetingConfig.getDefault([(IdentifierType.fromOS(system)): randomIfa]) + def account = createAccountWithRequestCorrectionConfig(bidRequest, targetingConfig) + accountDao.save(account) + + and: "Flash metrics" + flushMetrics(prebidServerStoredCacheService) + + when: "PBS processes auction request" + prebidServerStoredCacheService.sendAuctionRequest(bidRequest) + + then: "PBS should update metrics for new saved text storage cache" + def metrics = prebidServerStoredCacheService.sendCollectedMetricsRequest() + assert metrics[METRIC_CREATIVE_READ_ERR] == 1 + + and: "No updates for success metrics" + assert !metrics[METRIC_CREATIVE_SIZE_TEXT] + assert !metrics[METRIC_CREATIVE_TTL_TEXT] + assert !metrics[METRIC_CREATIVE_READ_OK] + } + + def "PBS should update error metrics when external service responded with invalid values"() { + given: "Default BidRequest with cache and device info" + def randomIfa = PBSUtils.randomString + def system = PBSUtils.getRandomEnum(OperatingSystem) + def bidRequest = getBidRequestForModuleCacheStorage(randomIfa, system) + + and: "Account with optable targeting module" + def targetingConfig = OptableTargetingConfig.getDefault([(IdentifierType.fromOS(system)): randomIfa]) + def account = createAccountWithRequestCorrectionConfig(bidRequest, targetingConfig) + accountDao.save(account) + + and: "Mocked external request" + storedCache.setTargetingResponse(bidRequest, targetingConfig) + storedCache.setCachingResponse(NOT_FOUND_404) + + and: "Flash metrics" + flushMetrics(prebidServerStoredCacheService) + + when: "PBS processes auction request" + prebidServerStoredCacheService.sendAuctionRequest(bidRequest) + + then: "PBS should update error metrics" + def metrics = prebidServerStoredCacheService.sendCollectedMetricsRequest() + assert metrics[METRIC_CREATIVE_WRITE_ERR] == 1 + + and: "No updates for success metrics" + assert !metrics[METRIC_CREATIVE_WRITE_OK] + } + + def "PBS should update metrics for new saved text storage cache when no cached requests"() { + given: "Default BidRequest with cache and device info" + def randomIfa = PBSUtils.randomString + def system = PBSUtils.getRandomEnum(OperatingSystem) + def bidRequest = getBidRequestForModuleCacheStorage(randomIfa, system) + + and: "Account with optable targeting module" + def targetingConfig = OptableTargetingConfig.getDefault([(IdentifierType.fromOS(system)): randomIfa]) + def account = createAccountWithRequestCorrectionConfig(bidRequest, targetingConfig) + accountDao.save(account) + + and: "Mocked external request" + def targetingResult = storedCache.setTargetingResponse(bidRequest, targetingConfig) + storedCache.setCachingResponse() + + and: "Flash metrics" + flushMetrics(prebidServerStoredCacheService) + + when: "PBS processes auction request" + prebidServerStoredCacheService.sendAuctionRequest(bidRequest) + + then: "PBS should update metrics for new saved text storage cache" + def metrics = prebidServerStoredCacheService.sendCollectedMetricsRequest() + assert metrics[METRIC_CREATIVE_SIZE_TEXT] == encodeBase64(encode(targetingResult).bytes).size() + assert metrics[METRIC_CREATIVE_TTL_TEXT] == targetingConfig.cache.ttlSeconds + assert metrics[METRIC_CREATIVE_WRITE_OK] == 1 + } + + def "PBS should update metrics for stored cached requests cache when proper record present"() { + given: "Current value of metric prebid cache" + def textInitialValue = getCurrentMetricValue(prebidServerStoredCacheService, METRIC_CREATIVE_SIZE_TEXT) + def ttlInitialValue = getCurrentMetricValue(prebidServerStoredCacheService, METRIC_CREATIVE_TTL_TEXT) + def writeInitialValue = getCurrentMetricValue(prebidServerStoredCacheService, METRIC_CREATIVE_WRITE_OK) + def readErrorInitialValue = getCurrentMetricValue(prebidServerStoredCacheService, METRIC_CREATIVE_READ_ERR) + def writeErrorInitialValue = getCurrentMetricValue(prebidServerStoredCacheService, METRIC_CREATIVE_WRITE_ERR) + + and: "Default BidRequest with cache and device info" + def randomIfa = PBSUtils.randomString + def system = PBSUtils.getRandomEnum(OperatingSystem) + def bidRequest = getBidRequestForModuleCacheStorage(randomIfa, system) + + and: "Account with optable targeting module" + def targetingConfig = OptableTargetingConfig.getDefault([(IdentifierType.fromOS(system)): randomIfa]) + def account = createAccountWithRequestCorrectionConfig(bidRequest, targetingConfig) + accountDao.save(account) + + and: "Mocked external request" + storedCache.setCachedTargetingResponse(bidRequest) + storedCache.setCachingResponse() + + and: "Flash metrics" + flushMetrics(prebidServerStoredCacheService) + + when: "PBS processes auction request" + prebidServerStoredCacheService.sendAuctionRequest(bidRequest) + + then: "PBS should update metrics for stored cached requests" + def metrics = prebidServerStoredCacheService.sendCollectedMetricsRequest() + assert metrics[METRIC_CREATIVE_READ_OK] == 1 + + and: "No updates for new saved text storage metrics" + assert metrics[METRIC_CREATIVE_SIZE_TEXT] == textInitialValue + assert metrics[METRIC_CREATIVE_TTL_TEXT] == ttlInitialValue + assert metrics[METRIC_CREATIVE_WRITE_OK] == writeInitialValue + + and: "No update for error metrics" + assert metrics[METRIC_CREATIVE_READ_ERR] == readErrorInitialValue + assert metrics[METRIC_CREATIVE_WRITE_ERR] == writeErrorInitialValue + } + + private static BidRequest getBidRequestForModuleCacheStorage(String ifa, OperatingSystem os) { + BidRequest.defaultBidRequest.tap { + it.enableCache() + it.user = new User(id: PBSUtils.randomString, data: [Data.defaultData], eids: [Eid.defaultEid]) + it.device = new Device(geo: Geo.FPDGeo, + ip: PBSUtils.getRandomEnum(PublicCountryIp.class).v4, + ifa: ifa, + os: os) + } + } + + private static Account createAccountWithRequestCorrectionConfig(BidRequest bidRequest, + OptableTargetingConfig optableTargetingConfig) { + + def pbsModulesConfig = new PbsModulesConfig(optableTargeting: optableTargetingConfig) + def accountHooksConfig = new AccountHooksConfiguration(modules: pbsModulesConfig) + def accountConfig = new AccountConfig(hooks: accountHooksConfig) + new Account(uuid: bidRequest.accountId, config: accountConfig) + } +} From a4db5fcd288224ede311d7cb5996abc434cc5e93 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Wed, 22 Oct 2025 19:26:10 +0300 Subject: [PATCH 08/10] fix tests --- .../tests/module/optabletargeting/CacheStorageSpec.groovy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy index 3d1665a101b..adf3ed5764c 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.tests.module.optabletargeting +import org.apache.commons.codec.binary.Base64 import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountHooksConfiguration import org.prebid.server.functional.model.config.IdentifierType @@ -132,7 +133,7 @@ class CacheStorageSpec extends ModuleBaseSpec { then: "PBS should update metrics for new saved text storage cache" def metrics = prebidServerStoredCacheService.sendCollectedMetricsRequest() - assert metrics[METRIC_CREATIVE_SIZE_TEXT] == encodeBase64(encode(targetingResult).bytes).size() + assert metrics[METRIC_CREATIVE_SIZE_TEXT] == new String(encodeBase64(encode(targetingResult).bytes)).size() assert metrics[METRIC_CREATIVE_TTL_TEXT] == targetingConfig.cache.ttlSeconds assert metrics[METRIC_CREATIVE_WRITE_OK] == 1 } @@ -186,6 +187,7 @@ class CacheStorageSpec extends ModuleBaseSpec { it.device = new Device(geo: Geo.FPDGeo, ip: PBSUtils.getRandomEnum(PublicCountryIp.class).v4, ifa: ifa, + ua: PBSUtils.randomString, os: os) } } From 3794dcdf265daa11ede85c7b26802d0dbfff4ce6 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 24 Oct 2025 16:56:45 +0300 Subject: [PATCH 09/10] fix test --- .../server/functional/model/config/CacheProperties.groovy | 2 +- .../functional/testcontainers/scaffolding/StoredCache.groovy | 3 ++- .../tests/module/optabletargeting/CacheStorageSpec.groovy | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/config/CacheProperties.groovy b/src/test/groovy/org/prebid/server/functional/model/config/CacheProperties.groovy index 028d5b203df..e16548b57c5 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/CacheProperties.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/CacheProperties.groovy @@ -15,7 +15,7 @@ class CacheProperties { static CacheProperties getDefault() { new CacheProperties().tap { enabled = true - ttlSeconds = PBSUtils.randomNumber + ttlSeconds = PBSUtils.getRandomNumber(0, 1000) } } } diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/StoredCache.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/StoredCache.groovy index 6f772af6d70..c8ad7caa924 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/StoredCache.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/StoredCache.groovy @@ -112,7 +112,8 @@ class StoredCache extends NetworkScaffolding { "&gdpr=${gdpr ? 1 : 0}", regs?.gpp ? "&gpp=${regs.gpp}" : null, regs?.gppSid ? "&gpp_sid=${regs.gppSid.first()}" : null, - config?.timeout ? "&timeout=${config.timeout}ms" : null].findAll().join('') + config?.timeout ? "&timeout=${config.timeout}ms" : null, + "&osdk=prebid-server"].findAll().join('') } } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy index adf3ed5764c..3ba0b8a95f4 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy @@ -1,6 +1,5 @@ package org.prebid.server.functional.tests.module.optabletargeting -import org.apache.commons.codec.binary.Base64 import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountHooksConfiguration import org.prebid.server.functional.model.config.IdentifierType @@ -134,8 +133,8 @@ class CacheStorageSpec extends ModuleBaseSpec { then: "PBS should update metrics for new saved text storage cache" def metrics = prebidServerStoredCacheService.sendCollectedMetricsRequest() assert metrics[METRIC_CREATIVE_SIZE_TEXT] == new String(encodeBase64(encode(targetingResult).bytes)).size() - assert metrics[METRIC_CREATIVE_TTL_TEXT] == targetingConfig.cache.ttlSeconds assert metrics[METRIC_CREATIVE_WRITE_OK] == 1 + assert metrics[METRIC_CREATIVE_TTL_TEXT] == targetingConfig.cache.ttlSeconds } def "PBS should update metrics for stored cached requests cache when proper record present"() { From efaec7c7243d5b275012250525e14d9a850384d9 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 24 Oct 2025 20:45:47 +0300 Subject: [PATCH 10/10] Fix flakiness test issue --- .../module/optabletargeting/CacheStorageSpec.groovy | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy index 3ba0b8a95f4..9a71d42ba7f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/module/optabletargeting/CacheStorageSpec.groovy @@ -110,7 +110,10 @@ class CacheStorageSpec extends ModuleBaseSpec { } def "PBS should update metrics for new saved text storage cache when no cached requests"() { - given: "Default BidRequest with cache and device info" + given: "Current value of metric prebid cache" + def okInitialValue = getCurrentMetricValue(prebidServerStoredCacheService, METRIC_CREATIVE_WRITE_OK) + + and: "Default BidRequest with cache and device info" def randomIfa = PBSUtils.randomString def system = PBSUtils.getRandomEnum(OperatingSystem) def bidRequest = getBidRequestForModuleCacheStorage(randomIfa, system) @@ -133,8 +136,10 @@ class CacheStorageSpec extends ModuleBaseSpec { then: "PBS should update metrics for new saved text storage cache" def metrics = prebidServerStoredCacheService.sendCollectedMetricsRequest() assert metrics[METRIC_CREATIVE_SIZE_TEXT] == new String(encodeBase64(encode(targetingResult).bytes)).size() - assert metrics[METRIC_CREATIVE_WRITE_OK] == 1 - assert metrics[METRIC_CREATIVE_TTL_TEXT] == targetingConfig.cache.ttlSeconds + assert metrics[METRIC_CREATIVE_WRITE_OK] == okInitialValue + 1 + + and: "PBS should include histogram metric" + assert metrics[METRIC_CREATIVE_TTL_TEXT] } def "PBS should update metrics for stored cached requests cache when proper record present"() {