Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.arextest.schedule.service.PlanConsumeService Maven / Gradle / Ivy
package com.arextest.schedule.service;
import com.arextest.model.replay.CaseSendScene;
import com.arextest.schedule.bizlog.BizLogger;
import com.arextest.schedule.common.CommonConstant;
import com.arextest.schedule.common.SendSemaphoreLimiter;
import com.arextest.schedule.comparer.CompareConfigService;
import com.arextest.schedule.dao.mongodb.ReplayActionCaseItemRepository;
import com.arextest.schedule.mdc.AbstractTracedRunnable;
import com.arextest.schedule.model.ExecutionStatus;
import com.arextest.schedule.model.PlanExecutionContext;
import com.arextest.schedule.model.ReplayActionCaseItem;
import com.arextest.schedule.model.ReplayActionItem;
import com.arextest.schedule.model.ReplayPlan;
import com.arextest.schedule.model.ReplayStatusType;
import com.arextest.schedule.model.plan.BuildReplayPlanType;
import com.arextest.schedule.model.plan.PlanStageEnum;
import com.arextest.schedule.model.plan.StageStatusEnum;
import com.arextest.schedule.planexecution.PlanExecutionContextProvider;
import com.arextest.schedule.planexecution.PlanExecutionMonitor;
import com.arextest.schedule.progress.ProgressEvent;
import com.arextest.schedule.progress.ProgressTracer;
import com.arextest.schedule.utils.ReplayParentBinder;
import com.arextest.schedule.utils.StageUtils;
import jakarta.annotation.Resource;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* @author jmo
* @since 2021/9/15
*/
@Slf4j
@Service
@SuppressWarnings({"rawtypes", "unchecked"})
public final class PlanConsumeService {
@Resource
private PlanConsumePrepareService planConsumePrepareService;
@Resource
private ReplayActionCaseItemRepository replayActionCaseItemRepository;
@Resource
private ReplayCaseTransmitServiceImpl replayCaseTransmitServiceRemoteImpl;
@Resource
private ExecutorService preloadExecutorService;
@Resource
private ProgressTracer progressTracer;
@Resource
private ProgressEvent progressEvent;
@Resource
private PlanExecutionContextProvider planExecutionContextProvider;
@Resource
private PlanExecutionMonitor planExecutionMonitorImpl;
@Resource
private CompareConfigService compareConfigService;
@Value("${arex.replay.threshold.errorBreakRatio:0.1}")
private double errorBreakRatio;
@Value("${arex.replay.threshold.continuousFail:40}")
private int continuousFailThreshold;
public void runAsyncConsume(ReplayPlan replayPlan) {
BizLogger.recordPlanAsyncStart(replayPlan);
preloadExecutorService.execute(new ReplayActionLoadingRunnableImpl(replayPlan));
}
private void consumePlan(ReplayPlan replayPlan) {
ExecutionStatus executionStatus = replayPlan.getPlanStatus();
long start;
long end;
progressTracer.initTotal(replayPlan);
if (replayPlan.isReRun()) {
// correct counter
progressTracer.reRunPlan(replayPlan);
}
int index = 0, total = replayPlan.getExecutionContexts().size();
for (PlanExecutionContext executionContext : replayPlan.getExecutionContexts()) {
index++;
// checkpoint: before each context
if (executionStatus.isAbnormal()) {
break;
}
executionContext.setExecutionStatus(executionStatus);
executionContext.setPlan(replayPlan);
// before context hook, may contain job like instructing target instance to prepare environment
start = System.currentTimeMillis();
planExecutionContextProvider.onBeforeContextExecution(executionContext, replayPlan);
end = System.currentTimeMillis();
LOGGER.info("context {} start hook took {} ms", executionContext.getContextName(),
end - start);
consumeContext(replayPlan, executionContext);
start = System.currentTimeMillis();
planExecutionContextProvider.onAfterContextExecution(executionContext, replayPlan);
end = System.currentTimeMillis();
LOGGER.info("context {} finish hook took {} ms", executionContext.getContextName(),
end - start);
StageStatusEnum stageStatusEnum = null;
Long endTime = null;
String format = StageUtils.RUN_MSG_FORMAT;
if (index == total) {
stageStatusEnum = StageStatusEnum.SUCCEEDED;
endTime = System.currentTimeMillis();
}
if (index == 1) {
format = StageUtils.RUN_MSG_FORMAT_SINGLE;
}
PlanStageEnum planStageEnum = replayPlan.isReRun() ? PlanStageEnum.RE_RUN : PlanStageEnum.RUN;
progressEvent.onReplayPlanStageUpdate(replayPlan, planStageEnum, stageStatusEnum,
null, endTime, String.format(format, total, index));
}
}
private void consumeContext(ReplayPlan replayPlan, PlanExecutionContext executionContext) {
if (executionContext.getExecutionStatus().isAbnormal()) {
return;
}
switch (executionContext.getActionType()) {
case SKIP_CASE_OF_CONTEXT:
// skip all cases of this context leaving the status as default
replayCaseTransmitServiceRemoteImpl.releaseCasesOfContext(replayPlan, executionContext);
break;
case NORMAL:
default:
this.consumeContextPaged(replayPlan, executionContext);
}
}
private void consumeContextPaged(ReplayPlan replayPlan, PlanExecutionContext executionContext) {
ExecutionStatus executionStatus = executionContext.getExecutionStatus();
List caseItems = Collections.emptyList();
final Set lastBatchIds = new HashSet<>();
while (true) {
// checkpoint: before sending page of cases
if (executionStatus.isAbnormal()) {
break;
}
ReplayActionCaseItem lastItem =
CollectionUtils.isNotEmpty(caseItems) ? caseItems.get(caseItems.size() - 1) : null;
caseItems = replayActionCaseItemRepository.waitingSendList(replayPlan.getId(),
CommonConstant.MAX_PAGE_SIZE,
executionContext.getContextCaseQuery(),
Optional.ofNullable(lastItem).map(ReplayActionCaseItem::getRecordTime).orElse(null));
caseItems.removeIf(caseItem -> lastBatchIds.contains(caseItem.getId()));
if (CollectionUtils.isEmpty(caseItems)) {
break;
}
lastBatchIds.clear();
lastBatchIds.addAll(
caseItems.stream().map(ReplayActionCaseItem::getId).collect(Collectors.toSet()));
ReplayParentBinder.setupCaseItemParent(caseItems, replayPlan);
caseItemPostProcess(caseItems);
replayCaseTransmitServiceRemoteImpl.send(caseItems, executionContext);
}
}
private void caseItemPostProcess(List cases) {
if (CollectionUtils.isEmpty(cases)) {
return;
}
boolean isMixedMode = Optional.ofNullable(cases.get(0).getParent())
.map(ReplayActionItem::getParent)
.map(ReplayPlan::getReplayPlanType)
.map(planType -> planType == BuildReplayPlanType.MIXED.getValue())
.orElse(false);
cases.forEach(caseItem -> {
caseItem.setCaseSendScene(isMixedMode ? CaseSendScene.MIXED_NORMAL : CaseSendScene.NORMAL);
});
}
private void finalizePlanStatus(ReplayPlan replayPlan) {
progressEvent.onReplayPlanStageUpdate(replayPlan, PlanStageEnum.FINISH, StageStatusEnum.ONGOING,
System.currentTimeMillis(), null);
ExecutionStatus executionStatus = replayPlan.getPlanStatus();
// finalize plan status
if (executionStatus.isCanceled()) {
progressEvent.onReplayPlanFinish(replayPlan, ReplayStatusType.CANCELLED);
BizLogger.recordPlanStatusChange(replayPlan, ReplayStatusType.CANCELLED);
} else if (executionStatus.isInterrupted()) {
progressEvent.onReplayPlanInterrupt(replayPlan, ReplayStatusType.FAIL_INTERRUPTED);
} else if (replayPlan.getCaseTotalCount() == 0) {
progressEvent.onReplayPlanFinish(replayPlan);
} else {
BizLogger.recordPlanDone(replayPlan);
}
progressEvent.onReplayPlanStageUpdate(replayPlan, PlanStageEnum.FINISH,
StageStatusEnum.SUCCEEDED, null, System.currentTimeMillis());
}
private final class ReplayActionLoadingRunnableImpl extends AbstractTracedRunnable {
private final ReplayPlan replayPlan;
private ReplayActionLoadingRunnableImpl(ReplayPlan replayPlan) {
this.replayPlan = replayPlan;
}
@Override
protected void doWithTracedRunning() {
try {
if (!initCaseCount()) {
return;
}
// init compareConfig,limiter, monitor
initReplayPlan();
// build context to send
progressEvent.onReplayPlanStageUpdate(replayPlan, PlanStageEnum.BUILD_CONTEXT,
StageStatusEnum.ONGOING,
System.currentTimeMillis(), null);
replayPlan.setExecutionContexts(planExecutionContextProvider.buildContext(replayPlan));
if (CollectionUtils.isEmpty(replayPlan.getExecutionContexts())) {
LOGGER.error("Invalid context built for plan {}", replayPlan);
replayPlan.setErrorMessage("Got empty execution context");
progressEvent.onReplayPlanInterrupt(replayPlan, ReplayStatusType.FAIL_INTERRUPTED);
progressEvent.onReplayPlanStageUpdate(replayPlan, PlanStageEnum.BUILD_CONTEXT,
StageStatusEnum.FAILED, null, System.currentTimeMillis());
return;
}
progressEvent.onReplayPlanStageUpdate(replayPlan, PlanStageEnum.BUILD_CONTEXT,
StageStatusEnum.SUCCEEDED, null, System.currentTimeMillis());
// process plan
PlanStageEnum planStageEnum =
replayPlan.isReRun() ? PlanStageEnum.RE_RUN : PlanStageEnum.RUN;
progressEvent.onReplayPlanStageUpdate(replayPlan, planStageEnum, StageStatusEnum.ONGOING,
System.currentTimeMillis(), null);
consumePlan(replayPlan);
// finalize exceptional status
finalizePlanStatus(replayPlan);
} catch (Throwable t) {
BizLogger.recordPlanException(replayPlan, t);
throw t;
} finally {
planExecutionMonitorImpl.deregister(replayPlan);
}
}
private boolean initCaseCount() {
if (replayPlan.isReRun()) {
return true;
}
long start = System.currentTimeMillis();
progressEvent.onReplayPlanStageUpdate(replayPlan, PlanStageEnum.LOADING_CASE,
StageStatusEnum.ONGOING, start, null);
int planSavedCaseSize = planConsumePrepareService.preparePlan(replayPlan);
long end = System.currentTimeMillis();
progressEvent.onReplayPlanStageUpdate(replayPlan, PlanStageEnum.LOADING_CASE,
StageStatusEnum.SUCCEEDED, null, end);
if (planSavedCaseSize == 0) {
LOGGER.warn("No case found, please change the time range and try again. {}", replayPlan.getId());
String message = "No case found, please change the time range and try again.";
progressEvent.onReplayPlanTerminate(replayPlan.getId(), message);
BizLogger.recordPlanStatusChange(replayPlan, ReplayStatusType.CANCELLED, message);
return false;
}
return true;
}
private void initReplayPlan() {
compareConfigService.preload(replayPlan);
// limiter shared for entire plan, max qps = maxQps per instance * min instance count
final SendSemaphoreLimiter qpsLimiter = new SendSemaphoreLimiter(
replayPlan.getReplaySendMaxQps(),
replayPlan.getMinInstanceCount());
qpsLimiter.setTotalTasks(replayPlan.getCaseTotalCount());
qpsLimiter.setReplayPlan(replayPlan);
qpsLimiter.setErrorBreakRatio(errorBreakRatio);
qpsLimiter.setContinuousFailThreshold(continuousFailThreshold);
replayPlan.setPlanStatus(ExecutionStatus.buildNormal(qpsLimiter));
replayPlan.setLimiter(qpsLimiter);
replayPlan.getReplayActionItemList()
.forEach(replayActionItem -> replayActionItem.setSendRateLimiter(qpsLimiter));
replayPlan.buildActionItemMap();
LOGGER.info("plan {} init with rate {}", replayPlan, qpsLimiter.getPermits());
}
}
}