Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exitpoint Stage #3564

Merged
merged 12 commits into from
Dec 4, 2024
65 changes: 1 addition & 64 deletions src/main/java/org/prebid/server/auction/ExchangeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,7 @@
import org.prebid.server.floors.PriceFloorAdjuster;
import org.prebid.server.floors.PriceFloorProcessor;
import org.prebid.server.hooks.execution.HookStageExecutor;
import org.prebid.server.hooks.execution.model.ExecutionAction;
import org.prebid.server.hooks.execution.model.ExecutionStatus;
import org.prebid.server.hooks.execution.model.GroupExecutionOutcome;
import org.prebid.server.hooks.execution.model.HookExecutionOutcome;
import org.prebid.server.hooks.execution.model.HookId;
import org.prebid.server.hooks.execution.model.HookStageExecutionResult;
import org.prebid.server.hooks.execution.model.Stage;
import org.prebid.server.hooks.execution.model.StageExecutionOutcome;
import org.prebid.server.hooks.v1.bidder.BidderRequestPayload;
import org.prebid.server.hooks.v1.bidder.BidderResponsePayload;
import org.prebid.server.json.JacksonMapper;
Expand Down Expand Up @@ -110,7 +103,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
Expand Down Expand Up @@ -221,8 +213,7 @@ public Future<AuctionContext> holdAuction(AuctionContext context) {
return processAuctionRequest(context)
.compose(this::invokeResponseHooks)
.map(AnalyticsTagsEnricher::enrichWithAnalyticsTags)
.map(HookDebugInfoEnricher::enrichWithHooksDebugInfo)
.map(this::updateHooksMetrics);
.map(HookDebugInfoEnricher::enrichWithHooksDebugInfo);
CTMBNara marked this conversation as resolved.
Show resolved Hide resolved
}

private Future<AuctionContext> processAuctionRequest(AuctionContext context) {
Expand Down Expand Up @@ -1374,58 +1365,4 @@ private static MetricName bidderErrorTypeToMetric(BidderError.Type errorType) {
case rejected_ipf, generic -> MetricName.unknown_error;
};
}

private AuctionContext updateHooksMetrics(AuctionContext context) {
final EnumMap<Stage, List<StageExecutionOutcome>> stageOutcomes =
context.getHookExecutionContext().getStageOutcomes();

final Account account = context.getAccount();

stageOutcomes.forEach((stage, outcomes) -> updateHooksStageMetrics(account, stage, outcomes));

// account might be null if request is rejected by the entrypoint hook
if (account != null) {
stageOutcomes.values().stream()
.flatMap(Collection::stream)
.map(StageExecutionOutcome::getGroups)
.flatMap(Collection::stream)
.map(GroupExecutionOutcome::getHooks)
.flatMap(Collection::stream)
.filter(hookOutcome -> hookOutcome.getAction() != ExecutionAction.no_invocation)
.collect(Collectors.groupingBy(
outcome -> outcome.getHookId().getModuleCode(),
Collectors.summingLong(HookExecutionOutcome::getExecutionTime)))
.forEach((moduleCode, executionTime) ->
metrics.updateAccountModuleDurationMetric(account, moduleCode, executionTime));
}

return context;
}

private void updateHooksStageMetrics(Account account, Stage stage, List<StageExecutionOutcome> stageOutcomes) {
stageOutcomes.stream()
.flatMap(stageOutcome -> stageOutcome.getGroups().stream())
.flatMap(groupOutcome -> groupOutcome.getHooks().stream())
.forEach(hookOutcome -> updateHookInvocationMetrics(account, stage, hookOutcome));
}

private void updateHookInvocationMetrics(Account account, Stage stage, HookExecutionOutcome hookOutcome) {
final HookId hookId = hookOutcome.getHookId();
final ExecutionStatus status = hookOutcome.getStatus();
final ExecutionAction action = hookOutcome.getAction();
final String moduleCode = hookId.getModuleCode();

metrics.updateHooksMetrics(
moduleCode,
stage,
hookId.getHookImplCode(),
status,
hookOutcome.getExecutionTime(),
action);

// account might be null if request is rejected by the entrypoint hook
if (account != null) {
metrics.updateAccountHooksMetrics(account, moduleCode, status, action);
}
}
}
81 changes: 81 additions & 0 deletions src/main/java/org/prebid/server/auction/HooksMetricsService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.prebid.server.auction;

import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.hooks.execution.model.ExecutionAction;
import org.prebid.server.hooks.execution.model.ExecutionStatus;
import org.prebid.server.hooks.execution.model.GroupExecutionOutcome;
import org.prebid.server.hooks.execution.model.HookExecutionOutcome;
import org.prebid.server.hooks.execution.model.HookId;
import org.prebid.server.hooks.execution.model.Stage;
import org.prebid.server.hooks.execution.model.StageExecutionOutcome;
import org.prebid.server.metric.Metrics;
import org.prebid.server.settings.model.Account;

import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class HooksMetricsService {

private final Metrics metrics;

public HooksMetricsService(Metrics metrics) {
this.metrics = Objects.requireNonNull(metrics);
}

public AuctionContext updateHooksMetrics(AuctionContext context) {
final EnumMap<Stage, List<StageExecutionOutcome>> stageOutcomes =
context.getHookExecutionContext().getStageOutcomes();

final Account account = context.getAccount();

stageOutcomes.forEach((stage, outcomes) -> updateHooksStageMetrics(account, stage, outcomes));

// account might be null if request is rejected by the entrypoint hook
if (account != null) {
stageOutcomes.values().stream()
.flatMap(Collection::stream)
.map(StageExecutionOutcome::getGroups)
.flatMap(Collection::stream)
.map(GroupExecutionOutcome::getHooks)
.flatMap(Collection::stream)
.filter(hookOutcome -> hookOutcome.getAction() != ExecutionAction.no_invocation)
.collect(Collectors.groupingBy(
outcome -> outcome.getHookId().getModuleCode(),
Collectors.summingLong(HookExecutionOutcome::getExecutionTime)))
.forEach((moduleCode, executionTime) ->
metrics.updateAccountModuleDurationMetric(account, moduleCode, executionTime));
}

return context;
}

private void updateHooksStageMetrics(Account account, Stage stage, List<StageExecutionOutcome> stageOutcomes) {
stageOutcomes.stream()
.flatMap(stageOutcome -> stageOutcome.getGroups().stream())
.flatMap(groupOutcome -> groupOutcome.getHooks().stream())
.forEach(hookOutcome -> updateHookInvocationMetrics(account, stage, hookOutcome));
}

private void updateHookInvocationMetrics(Account account, Stage stage, HookExecutionOutcome hookOutcome) {
final HookId hookId = hookOutcome.getHookId();
final ExecutionStatus status = hookOutcome.getStatus();
final ExecutionAction action = hookOutcome.getAction();
final String moduleCode = hookId.getModuleCode();

metrics.updateHooksMetrics(
moduleCode,
stage,
hookId.getHookImplCode(),
status,
hookOutcome.getExecutionTime(),
action);

// account might be null if request is rejected by the entrypoint hook
if (account != null) {
metrics.updateAccountHooksMetrics(account, moduleCode, status, action);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ private static HttpRequestContext toHttpRequest(HookStageExecutionResult<Entrypo
}

return HttpRequestContext.builder()
.httpMethod(routingContext.request().method())
.absoluteUri(routingContext.request().absoluteURI())
.queryParams(stageResult.getPayload().queryParams())
.headers(stageResult.getPayload().headers())
Expand Down
87 changes: 70 additions & 17 deletions src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import org.prebid.server.analytics.model.AmpEvent;
import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator;
import org.prebid.server.auction.AmpResponsePostProcessor;
import org.prebid.server.auction.AnalyticsTagsEnricher;
import org.prebid.server.auction.ExchangeService;
import org.prebid.server.auction.HookDebugInfoEnricher;
import org.prebid.server.auction.HooksMetricsService;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.Tuple2;
import org.prebid.server.auction.requestfactory.AmpRequestFactory;
Expand All @@ -34,6 +37,8 @@
import org.prebid.server.exception.InvalidRequestException;
import org.prebid.server.exception.PreBidException;
import org.prebid.server.exception.UnauthorizedAccountException;
import org.prebid.server.hooks.execution.HookStageExecutor;
import org.prebid.server.hooks.execution.model.HookStageExecutionResult;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.log.ConditionalLogger;
import org.prebid.server.log.HttpInteractionLogger;
Expand Down Expand Up @@ -81,38 +86,44 @@ public class AmpHandler implements ApplicationResource {
private final ExchangeService exchangeService;
private final AnalyticsReporterDelegator analyticsDelegator;
private final Metrics metrics;
private final HooksMetricsService hooksMetricsService;
private final Clock clock;
private final BidderCatalog bidderCatalog;
private final Set<String> biddersSupportingCustomTargeting;
private final AmpResponsePostProcessor ampResponsePostProcessor;
private final HttpInteractionLogger httpInteractionLogger;
private final PrebidVersionProvider prebidVersionProvider;
private final HookStageExecutor hookStageExecutor;
private final JacksonMapper mapper;
private final double logSamplingRate;

public AmpHandler(AmpRequestFactory ampRequestFactory,
ExchangeService exchangeService,
AnalyticsReporterDelegator analyticsDelegator,
Metrics metrics,
HooksMetricsService hooksMetricsService,
Clock clock,
BidderCatalog bidderCatalog,
Set<String> biddersSupportingCustomTargeting,
AmpResponsePostProcessor ampResponsePostProcessor,
HttpInteractionLogger httpInteractionLogger,
PrebidVersionProvider prebidVersionProvider,
HookStageExecutor hookStageExecutor,
JacksonMapper mapper,
double logSamplingRate) {

this.ampRequestFactory = Objects.requireNonNull(ampRequestFactory);
this.exchangeService = Objects.requireNonNull(exchangeService);
this.analyticsDelegator = Objects.requireNonNull(analyticsDelegator);
this.metrics = Objects.requireNonNull(metrics);
this.hooksMetricsService = Objects.requireNonNull(hooksMetricsService);
this.clock = Objects.requireNonNull(clock);
this.bidderCatalog = Objects.requireNonNull(bidderCatalog);
this.biddersSupportingCustomTargeting = Objects.requireNonNull(biddersSupportingCustomTargeting);
this.ampResponsePostProcessor = Objects.requireNonNull(ampResponsePostProcessor);
this.httpInteractionLogger = Objects.requireNonNull(httpInteractionLogger);
this.prebidVersionProvider = Objects.requireNonNull(prebidVersionProvider);
this.hookStageExecutor = Objects.requireNonNull(hookStageExecutor);
this.mapper = Objects.requireNonNull(mapper);
this.logSamplingRate = logSamplingRate;
}
Expand All @@ -134,15 +145,18 @@ public void handle(RoutingContext routingContext) {
.httpContext(HttpRequestContext.from(routingContext));

ampRequestFactory.fromRequest(routingContext, startTime)

.map(context -> addToEvent(context, ampEventBuilder::auctionContext, context))
.map(this::updateAppAndNoCookieAndImpsMetrics)

.compose(exchangeService::holdAuction)
.map(context -> addToEvent(context, ampEventBuilder::auctionContext, context))
.map(context -> addToEvent(context.getBidResponse(), ampEventBuilder::bidResponse, context))
.compose(context -> prepareAmpResponse(context, routingContext))
.map(result -> addToEvent(result.getLeft().getTargeting(), ampEventBuilder::targeting, result))
.compose(context -> prepareSuccessfulResponse(context, routingContext, ampEventBuilder))
.compose(this::invokeExitpointHooks)
.map(context -> addToEvent(context.getAuctionContext(), ampEventBuilder::auctionContext, context))
.map(context -> addToEvent(
context.getAuctionContext().getBidResponse(),
ampEventBuilder::bidResponse,
context))
.onComplete(responseResult -> handleResult(responseResult, ampEventBuilder, routingContext, startTime));
}

Expand All @@ -166,8 +180,44 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context
return context;
}

private Future<RawResponseContext> prepareSuccessfulResponse(AuctionContext auctionContext,
RoutingContext routingContext,
AmpEvent.AmpEventBuilder ampEventBuilder) {

final String origin = originFrom(routingContext);
final MultiMap responseHeaders = getCommonResponseHeaders(routingContext, origin)
.add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON);

return prepareAmpResponse(auctionContext, routingContext)
.map(result -> addToEvent(result.getLeft().getTargeting(), ampEventBuilder::targeting, result))
.map(result -> RawResponseContext.builder()
.responseBody(mapper.encodeToString(result.getLeft()))
.responseHeaders(responseHeaders)
.auctionContext(auctionContext)
.build());
}

private Future<RawResponseContext> invokeExitpointHooks(RawResponseContext rawResponseContext) {
final AuctionContext auctionContext = rawResponseContext.getAuctionContext();
return hookStageExecutor.executeExitpointStage(
rawResponseContext.getResponseHeaders(),
rawResponseContext.getResponseBody(),
auctionContext)
.map(HookStageExecutionResult::getPayload)
.compose(payload -> Future.succeededFuture(auctionContext)
.map(AnalyticsTagsEnricher::enrichWithAnalyticsTags)
.map(HookDebugInfoEnricher::enrichWithHooksDebugInfo)
.map(hooksMetricsService::updateHooksMetrics)
.map(context -> RawResponseContext.builder()
.auctionContext(context)
.responseHeaders(payload.responseHeaders())
.responseBody(payload.responseBody())
.build()));
}

private Future<Tuple2<AmpResponse, AuctionContext>> prepareAmpResponse(AuctionContext context,
RoutingContext routingContext) {

final BidRequest bidRequest = context.getBidRequest();
final BidResponse bidResponse = context.getBidResponse();
final AmpResponse ampResponse = toAmpResponse(bidResponse);
Expand Down Expand Up @@ -271,12 +321,13 @@ private static ExtAmpVideoResponse extResponseFrom(BidResponse bidResponse) {
: null;
}

private void handleResult(AsyncResult<Tuple2<AmpResponse, AuctionContext>> responseResult,
private void handleResult(AsyncResult<RawResponseContext> responseResult,
AmpEvent.AmpEventBuilder ampEventBuilder,
RoutingContext routingContext,
long startTime) {

final boolean responseSucceeded = responseResult.succeeded();
final RawResponseContext rawResponseContext = responseSucceeded ? responseResult.result() : null;

final MetricName metricRequestStatus;
final List<String> errorMessages;
Expand All @@ -287,15 +338,17 @@ private void handleResult(AsyncResult<Tuple2<AmpResponse, AuctionContext>> respo
ampEventBuilder.origin(origin);

final HttpServerResponse response = routingContext.response();
enrichResponseWithCommonHeaders(routingContext, origin);
final MultiMap responseHeaders = response.headers();

if (responseSucceeded) {
metricRequestStatus = MetricName.ok;
errorMessages = Collections.emptyList();

status = HttpResponseStatus.OK;
enrichWithSuccessfulHeaders(response);
body = mapper.encodeToString(responseResult.result().getLeft());

rawResponseContext.getResponseHeaders()
.forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty(
responseHeaders, header.getKey(), header.getValue()));
body = rawResponseContext.getResponseBody();
} else {
final Throwable exception = responseResult.cause();
if (exception instanceof InvalidRequestException invalidRequestException) {
Expand Down Expand Up @@ -351,12 +404,15 @@ private void handleResult(AsyncResult<Tuple2<AmpResponse, AuctionContext>> respo
status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
body = "Critical error while running the auction: " + message;
}

getCommonResponseHeaders(routingContext, origin)
.forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty(
responseHeaders, header.getKey(), header.getValue()));
}

final int statusCode = status.code();
final AmpEvent ampEvent = ampEventBuilder.status(statusCode).errors(errorMessages).build();

final AuctionContext auctionContext = responseSucceeded ? responseResult.result().getRight() : null;
final AuctionContext auctionContext = ampEvent.getAuctionContext();

final PrivacyContext privacyContext = auctionContext != null ? auctionContext.getPrivacyContext() : null;
final TcfContext tcfContext = privacyContext != null ? privacyContext.getTcfContext() : TcfContext.empty();
Expand Down Expand Up @@ -406,8 +462,8 @@ private void handleResponseException(Throwable exception) {
metrics.updateRequestTypeMetric(REQUEST_TYPE_METRIC, MetricName.networkerr);
}

private void enrichResponseWithCommonHeaders(RoutingContext routingContext, String origin) {
final MultiMap responseHeaders = routingContext.response().headers();
private MultiMap getCommonResponseHeaders(RoutingContext routingContext, String origin) {
final MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap();
HttpUtil.addHeaderIfValueIsNotEmpty(
responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord());

Expand All @@ -419,10 +475,7 @@ private void enrichResponseWithCommonHeaders(RoutingContext routingContext, Stri
// Add AMP headers
responseHeaders.add("AMP-Access-Control-Allow-Source-Origin", origin)
.add("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin");
}

private void enrichWithSuccessfulHeaders(HttpServerResponse response) {
final MultiMap headers = response.headers();
headers.add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON);
return responseHeaders;
}
}
Loading
Loading