From e53ca0e8ee4184f24a49b1097eb075011bf5988e Mon Sep 17 00:00:00 2001 From: Sergii Chernysh Date: Tue, 8 Jun 2021 17:43:02 +0300 Subject: [PATCH] Implement hooks metrics submission --- .../server/auction/ExchangeService.java | 60 ++++++- .../prebid/server/metric/AccountMetrics.java | 6 + .../prebid/server/metric/HookImplMetrics.java | 32 ++++ .../server/metric/HookSuccessMetrics.java | 24 +++ .../prebid/server/metric/HooksMetrics.java | 55 ++++++ .../org/prebid/server/metric/MetricName.java | 12 +- .../org/prebid/server/metric/Metrics.java | 79 +++++++++ .../prebid/server/metric/ModuleMetrics.java | 49 ++++++ .../prebid/server/metric/StageMetrics.java | 56 ++++++ .../server/auction/ExchangeServiceTest.java | 121 +++++++++++++ .../org/prebid/server/metric/MetricsTest.java | 160 ++++++++++++++++++ 11 files changed, 652 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/prebid/server/metric/HookImplMetrics.java create mode 100644 src/main/java/org/prebid/server/metric/HookSuccessMetrics.java create mode 100644 src/main/java/org/prebid/server/metric/HooksMetrics.java create mode 100644 src/main/java/org/prebid/server/metric/ModuleMetrics.java create mode 100644 src/main/java/org/prebid/server/metric/StageMetrics.java diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index baa456f336b..b7f7efb330d 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -43,8 +43,11 @@ import org.prebid.server.exception.PreBidException; import org.prebid.server.execution.Timeout; 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; @@ -92,6 +95,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -176,7 +180,8 @@ public ExchangeService(long expectedCacheTime, */ public Future holdAuction(AuctionContext context) { return processAuctionRequest(context) - .map(bidResponse -> enrichWithHooksDebugInfo(bidResponse, context)); + .map(bidResponse -> enrichWithHooksDebugInfo(bidResponse, context)) + .map(bidResponse -> updateHooksMetrics(context, bidResponse)); } private Future processAuctionRequest(AuctionContext context) { @@ -1478,6 +1483,59 @@ private static ExtModulesTraceInvocationResult toTraceInvocationResult(HookExecu .build(); } + private T updateHooksMetrics(AuctionContext context, T result) { + final EnumMap stageOutcomes = + context.getHookExecutionContext().getStageOutcomes(); + + final Account account = context.getAccount(); + + stageOutcomes.forEach((stage, outcome) -> updateHooksStageMetrics(account, stage, outcome)); + + // account might be null if request is rejected by the entrypoint hook + if (account != null) { + final String accountId = account.getId(); + + stageOutcomes.values().stream() + .map(StageExecutionOutcome::getGroups) + .flatMap(Collection::stream) + .map(GroupExecutionOutcome::getHooks) + .flatMap(Collection::stream) + .collect(Collectors.groupingBy( + outcome -> outcome.getHookId().getModuleCode(), + Collectors.summingLong(HookExecutionOutcome::getExecutionTime))) + .forEach((moduleCode, executionTime) -> + metrics.updateAccountModuleDurationMetric(accountId, moduleCode, executionTime)); + } + + return result; + } + + private void updateHooksStageMetrics(Account account, Stage stage, StageExecutionOutcome 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.getId(), moduleCode, status, action); + } + } + private List nullIfEmpty(List value) { return CollectionUtils.isEmpty(value) ? null : value; } diff --git a/src/main/java/org/prebid/server/metric/AccountMetrics.java b/src/main/java/org/prebid/server/metric/AccountMetrics.java index 622e08f96a9..2cedd43d7ca 100644 --- a/src/main/java/org/prebid/server/metric/AccountMetrics.java +++ b/src/main/java/org/prebid/server/metric/AccountMetrics.java @@ -21,6 +21,7 @@ class AccountMetrics extends UpdatableMetrics { private final RequestMetrics requestsMetrics; private final CacheMetrics cacheMetrics; private final ResponseMetrics responseMetrics; + private final HooksMetrics hooksMetrics; AccountMetrics(MetricRegistry metricRegistry, CounterType counterType, String account) { super(Objects.requireNonNull(metricRegistry), Objects.requireNonNull(counterType), @@ -32,6 +33,7 @@ class AccountMetrics extends UpdatableMetrics { requestsMetrics = new RequestMetrics(metricRegistry, counterType, createPrefix(account)); cacheMetrics = new CacheMetrics(metricRegistry, counterType, createPrefix(account)); responseMetrics = new ResponseMetrics(metricRegistry, counterType, createPrefix(account)); + hooksMetrics = new HooksMetrics(metricRegistry, counterType, createPrefix(account)); } private static String createPrefix(String account) { @@ -61,4 +63,8 @@ CacheMetrics cache() { ResponseMetrics response() { return responseMetrics; } + + HooksMetrics hooks() { + return hooksMetrics; + } } diff --git a/src/main/java/org/prebid/server/metric/HookImplMetrics.java b/src/main/java/org/prebid/server/metric/HookImplMetrics.java new file mode 100644 index 00000000000..b5e488e9222 --- /dev/null +++ b/src/main/java/org/prebid/server/metric/HookImplMetrics.java @@ -0,0 +1,32 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; + +import java.util.Objects; +import java.util.function.Function; + +class HookImplMetrics extends UpdatableMetrics { + + private final HookSuccessMetrics successMetrics; + + HookImplMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix, String hookImplCode) { + super( + Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(createPrefix(Objects.requireNonNull(prefix), Objects.requireNonNull(hookImplCode)))); + + successMetrics = new HookSuccessMetrics(metricRegistry, counterType, createPrefix(prefix, hookImplCode)); + } + + private static Function nameCreator(String prefix) { + return metricName -> String.format("%s.%s", prefix, metricName.toString()); + } + + private static String createPrefix(String prefix, String stage) { + return String.format("%s.hook.%s", prefix, stage); + } + + HookSuccessMetrics success() { + return successMetrics; + } +} diff --git a/src/main/java/org/prebid/server/metric/HookSuccessMetrics.java b/src/main/java/org/prebid/server/metric/HookSuccessMetrics.java new file mode 100644 index 00000000000..64daddd9069 --- /dev/null +++ b/src/main/java/org/prebid/server/metric/HookSuccessMetrics.java @@ -0,0 +1,24 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; + +import java.util.Objects; +import java.util.function.Function; + +class HookSuccessMetrics extends UpdatableMetrics { + + HookSuccessMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { + super( + Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(createPrefix(Objects.requireNonNull(prefix)))); + } + + private static Function nameCreator(String prefix) { + return metricName -> String.format("%s.%s", prefix, metricName.toString()); + } + + private static String createPrefix(String prefix) { + return String.format("%s.success", prefix); + } +} diff --git a/src/main/java/org/prebid/server/metric/HooksMetrics.java b/src/main/java/org/prebid/server/metric/HooksMetrics.java new file mode 100644 index 00000000000..6703f11dbd0 --- /dev/null +++ b/src/main/java/org/prebid/server/metric/HooksMetrics.java @@ -0,0 +1,55 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +class HooksMetrics extends UpdatableMetrics { + + // not thread-safe maps are intentionally used here because it's harmless in this particular case - eventually + // this all boils down to metrics lookup by underlying metric registry and that operation is guaranteed to be + // thread-safe + private final Function moduleMetricsCreator; + private final Map moduleMetrics; + + HooksMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) { + super( + Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(createPrefix(Objects.requireNonNull(prefix)))); + + moduleMetricsCreator = moduleCode -> + new ModuleMetrics(metricRegistry, counterType, createPrefix(prefix), moduleCode); + moduleMetrics = new HashMap<>(); + } + + HooksMetrics(MetricRegistry metricRegistry, CounterType counterType) { + super( + Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(createPrefix())); + + moduleMetricsCreator = moduleCode -> + new ModuleMetrics(metricRegistry, counterType, createPrefix(), moduleCode); + moduleMetrics = new HashMap<>(); + } + + private static Function nameCreator(String prefix) { + return metricName -> String.format("%s.%s", prefix, metricName.toString()); + } + + private static String createPrefix(String prefix) { + return String.format("%s.%s", prefix, createPrefix()); + } + + private static String createPrefix() { + return "modules"; + } + + ModuleMetrics module(String moduleCode) { + return moduleMetrics.computeIfAbsent(moduleCode, moduleMetricsCreator); + } +} diff --git a/src/main/java/org/prebid/server/metric/MetricName.java b/src/main/java/org/prebid/server/metric/MetricName.java index 96879485326..90e35ead331 100644 --- a/src/main/java/org/prebid/server/metric/MetricName.java +++ b/src/main/java/org/prebid/server/metric/MetricName.java @@ -121,7 +121,17 @@ public enum MetricName { initialize, update, hit, - miss; + miss, + + // hooks + call, + success, + noop, + reject, + unknown, + failure, + execution_error("execution-error"), + duration; private final String name; diff --git a/src/main/java/org/prebid/server/metric/Metrics.java b/src/main/java/org/prebid/server/metric/Metrics.java index 3c3cf9ef8bc..15cf7e858e5 100644 --- a/src/main/java/org/prebid/server/metric/Metrics.java +++ b/src/main/java/org/prebid/server/metric/Metrics.java @@ -2,6 +2,9 @@ import com.codahale.metrics.MetricRegistry; import com.iab.openrtb.request.Imp; +import org.prebid.server.hooks.execution.model.ExecutionAction; +import org.prebid.server.hooks.execution.model.ExecutionStatus; +import org.prebid.server.hooks.execution.model.Stage; import org.prebid.server.metric.model.AccountMetricsVerbosityLevel; import java.util.ArrayList; @@ -48,6 +51,7 @@ public class Metrics extends UpdatableMetrics { private final TimeoutNotificationMetrics timeoutNotificationMetrics; private final CurrencyRatesMetrics currencyRatesMetrics; private final Map settingsCacheMetrics; + private final HooksMetrics hooksMetrics; public Metrics(MetricRegistry metricRegistry, CounterType counterType, AccountMetricsVerbosity accountMetricsVerbosity) { @@ -77,6 +81,7 @@ public Metrics(MetricRegistry metricRegistry, CounterType counterType, timeoutNotificationMetrics = new TimeoutNotificationMetrics(metricRegistry, counterType); currencyRatesMetrics = new CurrencyRatesMetrics(metricRegistry, counterType); settingsCacheMetrics = new HashMap<>(); + hooksMetrics = new HooksMetrics(metricRegistry, counterType); } RequestStatusMetrics forRequestType(MetricName requestType) { @@ -127,6 +132,10 @@ SettingsCacheMetrics forSettingsCacheType(MetricName type) { return settingsCacheMetrics.computeIfAbsent(type, settingsCacheMetricsCreator); } + HooksMetrics hooks() { + return hooksMetrics; + } + public void updateAppAndNoCookieAndImpsRequestedMetrics(boolean isApp, boolean liveUidsPresent, int numImps) { if (isApp) { incCounter(MetricName.app_requests); @@ -504,4 +513,74 @@ public void updateSettingsCacheRefreshErrorMetric(MetricName cacheType, MetricNa public void updateSettingsCacheEventMetric(MetricName cacheType, MetricName event) { forSettingsCacheType(cacheType).incCounter(event); } + + public void updateHooksMetrics( + String moduleCode, + Stage stage, + String hookImplCode, + ExecutionStatus status, + Long executionTime, + ExecutionAction action) { + + final HookImplMetrics hookImplMetrics = hooks().module(moduleCode).stage(stage).hookImpl(hookImplCode); + + hookImplMetrics.incCounter(MetricName.call); + if (status == ExecutionStatus.success) { + hookImplMetrics.success().incCounter(HookMetricMapper.fromAction(action)); + } else { + hookImplMetrics.incCounter(HookMetricMapper.fromStatus(status)); + } + hookImplMetrics.updateTimer(MetricName.duration, executionTime); + } + + public void updateAccountHooksMetrics( + String accountId, + String moduleCode, + ExecutionStatus status, + ExecutionAction action) { + + if (accountMetricsVerbosity.forAccount(accountId).isAtLeast(AccountMetricsVerbosityLevel.detailed)) { + final ModuleMetrics accountModuleMetrics = forAccount(accountId).hooks().module(moduleCode); + + accountModuleMetrics.incCounter(MetricName.call); + if (status == ExecutionStatus.success) { + accountModuleMetrics.success().incCounter(HookMetricMapper.fromAction(action)); + } else { + accountModuleMetrics.incCounter(MetricName.failure); + } + } + } + + public void updateAccountModuleDurationMetric(String accountId, String moduleCode, Long executionTime) { + if (accountMetricsVerbosity.forAccount(accountId).isAtLeast(AccountMetricsVerbosityLevel.detailed)) { + forAccount(accountId).hooks().module(moduleCode).updateTimer(MetricName.duration, executionTime); + } + } + + private static class HookMetricMapper { + + private static final EnumMap STATUS_TO_METRIC = + new EnumMap<>(ExecutionStatus.class); + private static final EnumMap ACTION_TO_METRIC = + new EnumMap<>(ExecutionAction.class); + + static { + STATUS_TO_METRIC.put(ExecutionStatus.failure, MetricName.failure); + STATUS_TO_METRIC.put(ExecutionStatus.timeout, MetricName.timeout); + STATUS_TO_METRIC.put(ExecutionStatus.invocation_failure, MetricName.execution_error); + STATUS_TO_METRIC.put(ExecutionStatus.execution_failure, MetricName.execution_error); + + ACTION_TO_METRIC.put(ExecutionAction.no_action, MetricName.noop); + ACTION_TO_METRIC.put(ExecutionAction.update, MetricName.update); + ACTION_TO_METRIC.put(ExecutionAction.reject, MetricName.reject); + } + + static MetricName fromStatus(ExecutionStatus status) { + return STATUS_TO_METRIC.getOrDefault(status, MetricName.unknown); + } + + static MetricName fromAction(ExecutionAction action) { + return ACTION_TO_METRIC.getOrDefault(action, MetricName.unknown); + } + } } diff --git a/src/main/java/org/prebid/server/metric/ModuleMetrics.java b/src/main/java/org/prebid/server/metric/ModuleMetrics.java new file mode 100644 index 00000000000..8e9ccc8b594 --- /dev/null +++ b/src/main/java/org/prebid/server/metric/ModuleMetrics.java @@ -0,0 +1,49 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; +import org.prebid.server.hooks.execution.model.Stage; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +class ModuleMetrics extends UpdatableMetrics { + + // not thread-safe maps are intentionally used here because it's harmless in this particular case - eventually + // this all boils down to metrics lookup by underlying metric registry and that operation is guaranteed to be + // thread-safe + private final Function stageMetricsCreator; + private final Map stageMetrics; + + private final HookSuccessMetrics successMetrics; + + ModuleMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix, String moduleCode) { + super( + Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(createPrefix(Objects.requireNonNull(prefix), Objects.requireNonNull(moduleCode)))); + + stageMetricsCreator = stage -> + new StageMetrics(metricRegistry, counterType, createPrefix(prefix, moduleCode), stage); + stageMetrics = new HashMap<>(); + + successMetrics = new HookSuccessMetrics(metricRegistry, counterType, createPrefix(prefix, moduleCode)); + } + + private static Function nameCreator(String prefix) { + return metricName -> String.format("%s.%s", prefix, metricName.toString()); + } + + private static String createPrefix(String prefix, String moduleCode) { + return String.format("%s.module.%s", prefix, moduleCode); + } + + StageMetrics stage(Stage stage) { + return stageMetrics.computeIfAbsent(stage, stageMetricsCreator); + } + + HookSuccessMetrics success() { + return successMetrics; + } +} diff --git a/src/main/java/org/prebid/server/metric/StageMetrics.java b/src/main/java/org/prebid/server/metric/StageMetrics.java new file mode 100644 index 00000000000..d5bbdcb50ca --- /dev/null +++ b/src/main/java/org/prebid/server/metric/StageMetrics.java @@ -0,0 +1,56 @@ +package org.prebid.server.metric; + +import com.codahale.metrics.MetricRegistry; +import org.prebid.server.hooks.execution.model.Stage; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +class StageMetrics extends UpdatableMetrics { + + private static final EnumMap STAGE_TO_METRIC = new EnumMap<>(Stage.class); + + static { + STAGE_TO_METRIC.put(Stage.entrypoint, "entrypoint"); + STAGE_TO_METRIC.put(Stage.raw_auction_request, "rawauction"); + STAGE_TO_METRIC.put(Stage.processed_auction_request, "procauction"); + STAGE_TO_METRIC.put(Stage.bidder_request, "bidrequest"); + STAGE_TO_METRIC.put(Stage.raw_bidder_response, "rawbidresponse"); + STAGE_TO_METRIC.put(Stage.processed_bidder_response, "procbidresponse"); + STAGE_TO_METRIC.put(Stage.auction_response, "auctionresponse"); + } + + private static final String UNKNOWN_STAGE = "unknown"; + + // not thread-safe maps are intentionally used here because it's harmless in this particular case - eventually + // this all boils down to metrics lookup by underlying metric registry and that operation is guaranteed to be + // thread-safe + private final Function hookImplMetricsCreator; + private final Map hookImplMetrics; + + StageMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix, Stage stage) { + super( + Objects.requireNonNull(metricRegistry), + Objects.requireNonNull(counterType), + nameCreator(createPrefix(Objects.requireNonNull(prefix), Objects.requireNonNull(stage)))); + + hookImplMetricsCreator = hookImplCode -> + new HookImplMetrics(metricRegistry, counterType, createPrefix(prefix, stage), hookImplCode); + hookImplMetrics = new HashMap<>(); + } + + private static Function nameCreator(String prefix) { + return metricName -> String.format("%s.%s", prefix, metricName.toString()); + } + + private static String createPrefix(String prefix, Stage stage) { + return String.format("%s.stage.%s", prefix, STAGE_TO_METRIC.getOrDefault(stage, UNKNOWN_STAGE)); + } + + HookImplMetrics hookImpl(String hookImpl) { + return hookImplMetrics.computeIfAbsent(hookImpl, hookImplMetricsCreator); + } +} diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index 596ec177ac9..8f271fa90a1 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -155,6 +155,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -2842,6 +2843,7 @@ public void shouldReturnBidResponseModifiedByAuctionResponseHooks() { public void shouldReturnEmptyBidResponseWhenRequestIsRejected() { // given final AuctionContext auctionContext = AuctionContext.builder() + .hookExecutionContext(HookExecutionContext.of(Endpoint.openrtb2_auction)) .debugContext(DebugContext.empty()) .requestRejected(true) .build(); @@ -3082,6 +3084,124 @@ public void shouldReturnBidResponseWithHooksDebugAndTraceInfoWhenRequestIsReject assertThat(extModules.getTrace()).isNotNull(); } + @Test + public void shouldIncrementHooksGlobalMetrics() { + // given + final AuctionContext auctionContext = AuctionContext.builder() + .hookExecutionContext(HookExecutionContext.of( + Endpoint.openrtb2_auction, + stageOutcomes())) + .debugContext(DebugContext.empty()) + .requestRejected(true) + .build(); + + // when + exchangeService.holdAuction(auctionContext); + + // then + verify(metrics, times(6)).updateHooksMetrics(anyString(), any(), any(), any(), any(), any()); + verify(metrics).updateHooksMetrics( + eq("module1"), + eq(Stage.entrypoint), + eq("hook1"), + eq(ExecutionStatus.success), + eq(4L), + eq(ExecutionAction.update)); + verify(metrics).updateHooksMetrics( + eq("module1"), + eq(Stage.entrypoint), + eq("hook2"), + eq(ExecutionStatus.invocation_failure), + eq(6L), + isNull()); + verify(metrics).updateHooksMetrics( + eq("module1"), + eq(Stage.entrypoint), + eq("hook2"), + eq(ExecutionStatus.success), + eq(4L), + eq(ExecutionAction.no_action)); + verify(metrics).updateHooksMetrics( + eq("module2"), + eq(Stage.entrypoint), + eq("hook1"), + eq(ExecutionStatus.timeout), + eq(6L), + isNull()); + verify(metrics).updateHooksMetrics( + eq("module3"), + eq(Stage.auction_response), + eq("hook1"), + eq(ExecutionStatus.success), + eq(4L), + eq(ExecutionAction.update)); + verify(metrics).updateHooksMetrics( + eq("module3"), + eq(Stage.auction_response), + eq("hook2"), + eq(ExecutionStatus.success), + eq(4L), + eq(ExecutionAction.no_action)); + verify(metrics, never()).updateAccountHooksMetrics(any(), any(), any(), any()); + verify(metrics, never()).updateAccountModuleDurationMetric(any(), any(), any()); + } + + @Test + public void shouldIncrementHooksGlobalAndAccountMetrics() { + // given + given(httpBidderRequester.requestBids(any(), any(), any(), any(), anyBoolean())) + .willReturn(Future.succeededFuture(givenSeatBid(emptyList()))); + + final BidRequest bidRequest = givenBidRequest(givenSingleImp(singletonMap("bidder", 2))); + final AuctionContext auctionContext = givenRequestContext(bidRequest).toBuilder() + .hookExecutionContext(HookExecutionContext.of( + Endpoint.openrtb2_auction, + stageOutcomes())) + .debugContext(DebugContext.empty()) + .build(); + + // when + exchangeService.holdAuction(auctionContext); + + // then + verify(metrics, times(6)).updateHooksMetrics(anyString(), any(), any(), any(), any(), any()); + verify(metrics, times(6)).updateAccountHooksMetrics(anyString(), any(), any(), any()); + verify(metrics).updateAccountHooksMetrics( + eq("accountId"), + eq("module1"), + eq(ExecutionStatus.success), + eq(ExecutionAction.update)); + verify(metrics).updateAccountHooksMetrics( + eq("accountId"), + eq("module1"), + eq(ExecutionStatus.invocation_failure), + isNull()); + verify(metrics).updateAccountHooksMetrics( + eq("accountId"), + eq("module1"), + eq(ExecutionStatus.success), + eq(ExecutionAction.no_action)); + verify(metrics).updateAccountHooksMetrics( + eq("accountId"), + eq("module2"), + eq(ExecutionStatus.timeout), + isNull()); + verify(metrics).updateAccountHooksMetrics( + eq("accountId"), + eq("module3"), + eq(ExecutionStatus.success), + eq(ExecutionAction.update)); + verify(metrics).updateAccountHooksMetrics( + eq("accountId"), + eq("module3"), + eq(ExecutionStatus.success), + eq(ExecutionAction.no_action)); + verify(metrics, times(3)).updateAccountModuleDurationMetric(anyString(), any(), any()); + verify(metrics).updateAccountModuleDurationMetric(eq("accountId"), eq("module1"), eq(14L)); + verify(metrics).updateAccountModuleDurationMetric(eq("accountId"), eq("module2"), eq(6L)); + verify(metrics).updateAccountModuleDurationMetric(eq("accountId"), eq("module3"), eq(8L)); + } + private AuctionContext givenRequestContext(BidRequest bidRequest) { return givenRequestContext(bidRequest, Account.builder().id("accountId").eventsEnabled(true).build()); } @@ -3095,6 +3215,7 @@ private AuctionContext givenRequestContext(BidRequest bidRequest, Account accoun .account(account) .requestTypeMetric(MetricName.openrtb2web) .timeout(timeout) + .hookExecutionContext(HookExecutionContext.of(Endpoint.openrtb2_auction)) .debugContext(DebugContext.empty()) .build(); } diff --git a/src/test/java/org/prebid/server/metric/MetricsTest.java b/src/test/java/org/prebid/server/metric/MetricsTest.java index 992ad27ddfb..0df420b3ce9 100644 --- a/src/test/java/org/prebid/server/metric/MetricsTest.java +++ b/src/test/java/org/prebid/server/metric/MetricsTest.java @@ -18,6 +18,9 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.prebid.server.hooks.execution.model.ExecutionAction; +import org.prebid.server.hooks.execution.model.ExecutionStatus; +import org.prebid.server.hooks.execution.model.Stage; import org.prebid.server.metric.model.AccountMetricsVerbosityLevel; import java.util.EnumMap; @@ -1042,6 +1045,163 @@ public void updateSettingsCacheEventMetricShouldIncrementMetric() { assertThat(metricRegistry.counter("settings.cache.account.hit").getCount()).isEqualTo(1); } + @Test + public void updateHooksMetricsShouldIncrementMetrics() { + // when + metrics.updateHooksMetrics( + "module1", Stage.entrypoint, "hook1", ExecutionStatus.success, 5L, ExecutionAction.update); + metrics.updateHooksMetrics( + "module1", Stage.raw_auction_request, "hook2", ExecutionStatus.success, 5L, ExecutionAction.no_action); + metrics.updateHooksMetrics( + "module1", + Stage.processed_auction_request, + "hook3", + ExecutionStatus.success, + 5L, + ExecutionAction.reject); + metrics.updateHooksMetrics( + "module2", Stage.bidder_request, "hook1", ExecutionStatus.failure, 6L, null); + metrics.updateHooksMetrics( + "module2", Stage.raw_bidder_response, "hook2", ExecutionStatus.timeout, 7L, null); + metrics.updateHooksMetrics( + "module2", Stage.processed_bidder_response, "hook3", ExecutionStatus.execution_failure, 5L, null); + metrics.updateHooksMetrics( + "module2", Stage.auction_response, "hook4", ExecutionStatus.invocation_failure, 5L, null); + + // then + assertThat(metricRegistry.counter("modules.module.module1.stage.entrypoint.hook.hook1.call") + .getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("modules.module.module1.stage.entrypoint.hook.hook1.success.update") + .getCount()) + .isEqualTo(1); + assertThat(metricRegistry.timer("modules.module.module1.stage.entrypoint.hook.hook1.duration").getCount()) + .isEqualTo(1); + + assertThat(metricRegistry.counter("modules.module.module1.stage.rawauction.hook.hook2.call").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("modules.module.module1.stage.rawauction.hook.hook2.success.noop").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.timer("modules.module.module1.stage.rawauction.hook.hook2.duration").getCount()) + .isEqualTo(1); + + assertThat(metricRegistry.counter("modules.module.module1.stage.procauction.hook.hook3.call").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("modules.module.module1.stage.procauction.hook.hook3.success.reject") + .getCount()) + .isEqualTo(1); + assertThat(metricRegistry.timer("modules.module.module1.stage.procauction.hook.hook3.duration").getCount()) + .isEqualTo(1); + + assertThat(metricRegistry.counter("modules.module.module2.stage.bidrequest.hook.hook1.call").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("modules.module.module2.stage.bidrequest.hook.hook1.failure").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.timer("modules.module.module2.stage.bidrequest.hook.hook1.duration").getCount()) + .isEqualTo(1); + + assertThat(metricRegistry.counter("modules.module.module2.stage.rawbidresponse.hook.hook2.call").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("modules.module.module2.stage.rawbidresponse.hook.hook2.timeout").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.timer("modules.module.module2.stage.rawbidresponse.hook.hook2.duration").getCount()) + .isEqualTo(1); + + assertThat(metricRegistry.counter("modules.module.module2.stage.procbidresponse.hook.hook3.call").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("modules.module.module2.stage.procbidresponse.hook.hook3.execution-error") + .getCount()) + .isEqualTo(1); + assertThat(metricRegistry.timer("modules.module.module2.stage.procbidresponse.hook.hook3.duration").getCount()) + .isEqualTo(1); + + assertThat(metricRegistry.counter("modules.module.module2.stage.auctionresponse.hook.hook4.call").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("modules.module.module2.stage.auctionresponse.hook.hook4.execution-error") + .getCount()) + .isEqualTo(1); + assertThat(metricRegistry.timer("modules.module.module2.stage.auctionresponse.hook.hook4.duration").getCount()) + .isEqualTo(1); + } + + @Test + public void updateAccountHooksMetricsShouldIncrementMetricsIfVerbosityIsDetailed() { + // given + given(accountMetricsVerbosity.forAccount(anyString())).willReturn(AccountMetricsVerbosityLevel.detailed); + + // when + metrics.updateAccountHooksMetrics( + "accountId", "module1", ExecutionStatus.success, ExecutionAction.update); + metrics.updateAccountHooksMetrics( + "accountId", "module2", ExecutionStatus.failure, null); + metrics.updateAccountHooksMetrics( + "accountId", "module3", ExecutionStatus.timeout, null); + + // then + assertThat(metricRegistry.counter("account.accountId.modules.module.module1.call").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("account.accountId.modules.module.module1.success.update").getCount()) + .isEqualTo(1); + + assertThat(metricRegistry.counter("account.accountId.modules.module.module2.call").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("account.accountId.modules.module.module2.failure").getCount()) + .isEqualTo(1); + + assertThat(metricRegistry.counter("account.accountId.modules.module.module3.call").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("account.accountId.modules.module.module3.failure").getCount()) + .isEqualTo(1); + } + + @Test + public void updateAccountHooksMetricsShouldNotIncrementMetricsIfVerbosityIsNotAtLeastDetailed() { + // given + given(accountMetricsVerbosity.forAccount(anyString())).willReturn(AccountMetricsVerbosityLevel.basic); + + // when + metrics.updateAccountHooksMetrics( + "accountId", "module1", ExecutionStatus.success, ExecutionAction.update); + + // then + assertThat(metricRegistry.counter("account.accountId.modules.module.module1.call").getCount()) + .isZero(); + assertThat(metricRegistry.counter("account.accountId.modules.module.module1.success.update").getCount()) + .isZero(); + } + + @Test + public void updateAccountModuleDurationMetricShouldIncrementMetricsIfVerbosityIsDetailed() { + // given + given(accountMetricsVerbosity.forAccount(anyString())).willReturn(AccountMetricsVerbosityLevel.detailed); + + // when + metrics.updateAccountModuleDurationMetric( + "accountId", "module1", 5L); + metrics.updateAccountModuleDurationMetric( + "accountId", "module2", 6L); + + // then + assertThat(metricRegistry.timer("account.accountId.modules.module.module1.duration").getCount()) + .isEqualTo(1); + assertThat(metricRegistry.timer("account.accountId.modules.module.module2.duration").getCount()) + .isEqualTo(1); + } + + @Test + public void updateAccountModuleDurationMetricShouldNotIncrementMetricsIfVerbosityIsNotAtLeastDetailed() { + // given + given(accountMetricsVerbosity.forAccount(anyString())).willReturn(AccountMetricsVerbosityLevel.basic); + + // when + metrics.updateAccountModuleDurationMetric( + "accountId", "module1", 5L); + + // then + assertThat(metricRegistry.timer("account.accountId.modules.module.module1.duration").getCount()) + .isZero(); + } + private void verifyCreatesConfiguredCounterType(Consumer metricsConsumer) { final EnumMap> counterTypeClasses = new EnumMap<>(CounterType.class); counterTypeClasses.put(CounterType.counter, Counter.class);