@JsonIgnore public Instant getEndTime() { return scopes.get(0).getEndTimeAsInstant(); }
@JsonIgnore public Instant getStartTime() { return scopes.get(0).getStartTimeAsInstant(); }
@JsonIgnore public Instant getLookBackAsInstant() { return Instant.ofEpochMilli(getLookBackAsDuration().toMillis()); } }
CanaryAnalysisExecutionRequest canaryAnalysisExecutionRequest = canaryAnalysisConfig.getExecutionRequest(); if (canaryAnalysisExecutionRequest.getScopes().isEmpty()) { throw new IllegalArgumentException("Canary stage configuration must contain at least one scope."); Instant start = Optional.ofNullable(canaryAnalysisExecutionRequest.getStartTime()).orElse(now(clock)); Instant endTime = canaryAnalysisExecutionRequest.getEndTime(); Duration lifetime = calculateLifetime(start, endTime, canaryAnalysisExecutionRequest); Duration analysisInterval = calculateAnalysisInterval(canaryAnalysisExecutionRequest, lifetime); if (canaryAnalysisExecutionRequest.getBeginCanaryAnalysisAfterAsInstant().isAfter(ZERO_AS_INSTANT)) { graph.append(stage -> { stage.setType(WaitStage.STAGE_TYPE); stage.setName("Warmup Wait"); stage.getContext().put("waitTime", canaryAnalysisExecutionRequest.getBeginCanaryAnalysisAfterAsDuration().getSeconds()); }); RunCanaryContext runCanaryContext = RunCanaryContext.builder() .application(canaryAnalysisConfig.getApplication()) .user(canaryAnalysisConfig.getUser()) .parentPipelineExecutionId(canaryAnalysisConfig.getParentPipelineExecutionId()) .canaryConfigId(canaryAnalysisConfig.getCanaryConfigId()) .metricsAccountName(canaryAnalysisConfig.getMetricsAccountName()) .storageAccountName(canaryAnalysisConfig.getStorageAccountName()) .canaryConfig(canaryAnalysisConfig.getCanaryConfig()) .scopes(buildRequestScopes(canaryAnalysisExecutionRequest, i, analysisInterval)) .scoreThresholds(canaryAnalysisExecutionRequest.getThresholds()) .siteLocal(canaryAnalysisExecutionRequest.getSiteLocal()) .build();
accountCredentialsRepository); if (canaryAnalysisAdhocExecutionRequest.getCanaryConfig() == null) { throw new IllegalArgumentException("canaryConfig must be provided for ad-hoc requests"); if (canaryAnalysisAdhocExecutionRequest.getExecutionRequest() == null) { throw new IllegalArgumentException("executionRequest must be provided for ad-hoc requests"); CanaryAnalysisConfig.builder() .user(Optional.ofNullable(application).orElse("anonymous")) .application(Optional.ofNullable(application).orElse(AD_HOC)) .parentPipelineExecutionId(parentPipelineExecutionId) .executionRequest(canaryAnalysisAdhocExecutionRequest.getExecutionRequest()) .metricsAccountName(resolvedMetricsAccountName) .storageAccountName(resolvedStorageAccountName) .canaryConfig(canaryAnalysisAdhocExecutionRequest.getCanaryConfig()) .build());
protected Map<String, CanaryScopePair> buildRequestScopes(CanaryAnalysisExecutionRequest config, long interval, Duration intervalDuration) { Map<String, CanaryScopePair> scopes = new HashMap<>(); config.getScopes().forEach(scope -> { ScopeTimeConfig scopeTimeConfig = calculateStartAndEndForJudgement(config, interval, intervalDuration); CanaryScope controlScope = new CanaryScope( scope.getControlScope(), scope.getControlLocation(), scopeTimeConfig.start, scopeTimeConfig.end, config.getStep().getSeconds(), scope.getExtendedScopeParams() ); CanaryScope experimentScope = new CanaryScope( scope.getExperimentScope(), scope.getExperimentLocation(), scopeTimeConfig.start, scopeTimeConfig.end, config.getStep().getSeconds(), scope.getExtendedScopeParams() ); CanaryScopePair canaryScopePair = new CanaryScopePair() .setControlScope(controlScope) .setExperimentScope(experimentScope); scopes.put(scope.getScopeName(), canaryScopePair); }); return scopes; }
@Test public void test_that_calculateStartAndEndForJudgement_has_expected_start_and_end_when_lookback_is_defined() { CanaryAnalysisExecutionRequest request = CanaryAnalysisExecutionRequest.builder() .lookbackMins(5L) .scopes(ImmutableList.of(CanaryAnalysisExecutionRequestScope.builder().build())) .build(); Duration intervalDuration = Duration.ofMinutes(5); for (int i = 1; i < 6; i++) { SetupAndExecuteCanariesStage.ScopeTimeConfig conf = stage.calculateStartAndEndForJudgement(request, i, intervalDuration); assertEquals(now.plus((i - 1) * 5, ChronoUnit.MINUTES), conf.getStart()); assertEquals(now.plus(i * 5, ChronoUnit.MINUTES), conf.getEnd()); } }
/** * Initiates the canary analysis execution Orca pipeline. * * @param canaryAnalysisConfig The configuration for the canary analysis execution. * @return Wrapper object around the execution id. */ public CanaryAnalysisExecutionResponse initiateCanaryAnalysisExecution(CanaryAnalysisConfig canaryAnalysisConfig) { String application = canaryAnalysisConfig.getApplication(); PipelineBuilder pipelineBuilder = new PipelineBuilder(application) .withName(CANARY_ANALYSIS_PIPELINE_NAME) .withPipelineConfigId(application + "-canary-analysis-referee-pipeline") .withStage( SetupAndExecuteCanariesStage.STAGE_TYPE, SetupAndExecuteCanariesStage.STAGE_DESCRIPTION, Maps.newHashMap(ImmutableMap.of( CANARY_ANALYSIS_CONFIG_CONTEXT_KEY, canaryAnalysisConfig ))); Execution pipeline = pipelineBuilder.withLimitConcurrent(false).build(); executionRepository.store(pipeline); try { executionLauncher.start(pipeline); } catch (Throwable t) { log.error("Failed to start pipeline", t); handleStartupFailure(pipeline, t); throw new RuntimeException("Failed to start the canary analysis pipeline execution"); } return CanaryAnalysisExecutionResponse.builder().canaryAnalysisExecutionId(pipeline.getId()).build(); }
@Test public void test_that_calculateLifetime_uses_supplied_lifetime_if_start_and_end_time_not_provided() { long lifetimeInMinutes = 5L; Instant now = Instant.now(); Instant later = null; CanaryAnalysisExecutionRequest request = CanaryAnalysisExecutionRequest.builder().lifetimeDurationMins(5L).build(); Duration actual = stage.calculateLifetime(now, later, request); Duration expected = Duration.ofMinutes(lifetimeInMinutes); assertEquals("The duration should be 5 minutes", expected, actual); }
@JsonIgnore public Duration getStep() { return Duration.ofSeconds(scopes.get(0).getStep()); }
@JsonIgnore public Instant getBeginCanaryAnalysisAfterAsInstant() { return Instant.ofEpochMilli(getBeginCanaryAnalysisAfterAsDuration().toMillis()); }
/** * Calculates the how often a canary judgement should be performed during the lifetime of the canary analysis execution. * * @param canaryAnalysisExecutionRequest The execution requests * @param lifetime The calculated lifetime of the canary analysis execution * @return How often a judgement should be performed */ protected Duration calculateAnalysisInterval(CanaryAnalysisExecutionRequest canaryAnalysisExecutionRequest, Duration lifetime) { Duration analysisInterval; if (canaryAnalysisExecutionRequest.getAnalysisIntervalMins() != null) { analysisInterval = Duration.ofMinutes(canaryAnalysisExecutionRequest.getAnalysisIntervalMins()); } else { analysisInterval = lifetime; } if (analysisInterval == ZERO || Instant.ofEpochMilli(analysisInterval.toMillis()).isAfter(Instant.ofEpochMilli(lifetime.toMillis()))) { analysisInterval = lifetime; } return analysisInterval; }
/** * Calculates the lifetime duration for the canary analysis execution. * * @param start The calculated start time for the execution * @param endTime The calculated endtime * @param canaryAnalysisExecutionRequest The execution request * @return The calculated duration of the canary analysis */ protected Duration calculateLifetime(Instant start, Instant endTime, CanaryAnalysisExecutionRequest canaryAnalysisExecutionRequest) { Duration lifetime; if (endTime != null) { lifetime = Duration.ofMinutes(start.until(endTime, MINUTES)); } else if (canaryAnalysisExecutionRequest.getLifetimeDuration() != null) { lifetime = canaryAnalysisExecutionRequest.getLifetimeDuration(); } else { throw new IllegalArgumentException("Canary stage configuration must include either `endTime` or `lifetimeDuration`."); } return lifetime; }
@Test public void test_that_calculateStartAndEndForJudgement_has_expected_start_and_end_when_nothing_is_set_in_the_scopes_with_warmup() { CanaryAnalysisExecutionRequest request = CanaryAnalysisExecutionRequest.builder() .scopes(ImmutableList.of(CanaryAnalysisExecutionRequestScope.builder().build())) .beginAfterMins(4L) .build(); Duration intervalDuration = Duration.ofMinutes(3); for (int i = 1; i < 6; i++) { SetupAndExecuteCanariesStage.ScopeTimeConfig conf = stage.calculateStartAndEndForJudgement(request, i, intervalDuration); assertEquals(now.plus(4, ChronoUnit.MINUTES), conf.getStart()); assertEquals(now.plus(i * 3, ChronoUnit.MINUTES).plus(4, ChronoUnit.MINUTES), conf.getEnd()); } }
@Test public void test_that_calculateLifetime_uses_supplied_start_and_end_time_if_provided() { int lifetimeInMinutes = 5; Instant now = Instant.now(); Instant later = now.plus(lifetimeInMinutes, ChronoUnit.MINUTES); CanaryAnalysisExecutionRequest request = CanaryAnalysisExecutionRequest.builder().build(); Duration actual = stage.calculateLifetime(now, later, request); Duration expected = Duration.ofMinutes(lifetimeInMinutes); assertEquals("The duration should be 5 minutes", expected, actual); }
@Test public void test_that_calculateStartAndEndForJudgement_has_expected_start_and_end_when_start_iso_only_is_defined() { int interval = 1; String startIso = "2018-12-17T20:56:39.689Z"; Duration lifetimeDuration = Duration.ofMinutes(3L); CanaryAnalysisExecutionRequest request = CanaryAnalysisExecutionRequest.builder() .scopes(ImmutableList.of(CanaryAnalysisExecutionRequestScope.builder().startTimeIso(startIso).build())) .build(); SetupAndExecuteCanariesStage.ScopeTimeConfig actual = stage.calculateStartAndEndForJudgement(request, interval, lifetimeDuration); assertEquals(Instant.parse(startIso), actual.getStart()); assertEquals(Instant.parse(startIso).plus(3L, ChronoUnit.MINUTES), actual.getEnd()); } }
@Test public void test_that_calculateStartAndEndForJudgement_has_expected_start_and_end_when_nothing_is_set_in_the_scopes() { CanaryAnalysisExecutionRequest request = CanaryAnalysisExecutionRequest.builder() .scopes(ImmutableList.of(CanaryAnalysisExecutionRequestScope.builder().build())) .build(); Duration intervalDuration = Duration.ofMinutes(3); for (int i = 1; i < 6; i++) { SetupAndExecuteCanariesStage.ScopeTimeConfig conf = stage.calculateStartAndEndForJudgement(request, i, intervalDuration); assertEquals(now, conf.getStart()); assertEquals(now.plus(i * 3, ChronoUnit.MINUTES), conf.getEnd()); } }
@Test public void test_that_calculateStartAndEndForJudgement_has_expected_start_and_end_when_start_and_end_are_supplied_in_the_scopes() { Instant definedStart = now.minus(5, ChronoUnit.MINUTES); Instant definedEnd = now.plus(20, ChronoUnit.MINUTES); CanaryAnalysisExecutionRequest request = CanaryAnalysisExecutionRequest.builder() .scopes(ImmutableList.of(CanaryAnalysisExecutionRequestScope.builder() .startTimeIso(definedStart.toString()) .endTimeIso(definedEnd.toString()) .build())) .build(); Duration intervalDuration = Duration.ofMinutes(3); for (int i = 1; i < 6; i++) { SetupAndExecuteCanariesStage.ScopeTimeConfig conf = stage.calculateStartAndEndForJudgement(request, i, intervalDuration); assertEquals(definedStart, conf.getStart()); assertEquals(definedStart.plus(i * 3, ChronoUnit.MINUTES), conf.getEnd()); } }
@Test public void test_that_calculateStartAndEndForJudgement_has_expected_start_and_end_when_start_and_end_are_supplied_in_the_scopes_with_warmup() { Instant definedStart = now.minus(5, ChronoUnit.MINUTES); Instant definedEnd = now.plus(20, ChronoUnit.MINUTES); CanaryAnalysisExecutionRequest request = CanaryAnalysisExecutionRequest.builder() .beginAfterMins(4L) .scopes(ImmutableList.of(CanaryAnalysisExecutionRequestScope.builder() .startTimeIso(definedStart.toString()) .endTimeIso(definedEnd.toString()) .build())) .build(); Duration intervalDuration = Duration.ofMinutes(3); for (int i = 1; i < 6; i++) { SetupAndExecuteCanariesStage.ScopeTimeConfig conf = stage.calculateStartAndEndForJudgement(request, i, intervalDuration); assertEquals(definedStart, conf.getStart()); assertEquals(definedStart.plus(i * 3, ChronoUnit.MINUTES), conf.getEnd()); } }