package com.linkedin.thirdeye.anomaly.grouping; import com.linkedin.thirdeye.anomaly.utils.AnomalyUtils; import com.linkedin.thirdeye.client.DAORegistry; import com.linkedin.thirdeye.datalayer.bao.AnomalyFunctionManager; import com.linkedin.thirdeye.datalayer.bao.ClassificationConfigManager; import com.linkedin.thirdeye.datalayer.bao.JobManager; import com.linkedin.thirdeye.datalayer.dto.AnomalyFunctionDTO; import com.linkedin.thirdeye.datalayer.dto.ClassificationConfigDTO; import com.linkedin.thirdeye.datalayer.dto.JobDTO; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class GroupingJobScheduler implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(GroupingJobScheduler.class); private static final DAORegistry DAO_REGISTRY = DAORegistry.getInstance(); private static final long maxLookbackLength = 259200000L; // 3 days private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); public void start() { LOG.info("Starting anomaly classification service"); this.scheduledExecutorService.scheduleWithFixedDelay(this, 0, 1, TimeUnit.MINUTES); } public void shutdown() { AnomalyUtils.safelyShutdownExecutionService(scheduledExecutorService, this.getClass()); } @Override public void run() { ClassificationConfigManager classificationConfigDAO = DAO_REGISTRY.getClassificationConfigDAO(); List<ClassificationConfigDTO> classificationConfigs = classificationConfigDAO.findActives(); for (ClassificationConfigDTO classificationConfig : classificationConfigs) { LOG.info("Running classifier: {}", classificationConfig); mainMetricTimeBasedGrouping(classificationConfig); } } private void mainMetricTimeBasedGrouping(ClassificationConfigDTO classificationConfig) { AnomalyFunctionManager anomalyFunctionDAO = DAO_REGISTRY.getAnomalyFunctionDAO(); JobManager jobDAO = DAO_REGISTRY.getJobDAO(); // TODO: Modularize this grouping logic and make it swappable // Get all involved anomaly functions that are activated List<AnomalyFunctionDTO> involvedAnomalyFunctions = new ArrayList<>(); List<Long> functionIdList = classificationConfig.getFunctionIdList(); for (long functionId : functionIdList) { AnomalyFunctionDTO anomalyFunctionDTO = anomalyFunctionDAO.findById(functionId); if (anomalyFunctionDTO.getIsActive()) { involvedAnomalyFunctions.add(anomalyFunctionDTO); } } // TODO: Determine if funnel effect has main metric. If it does not, then remove the block below if (!functionIdList.contains(classificationConfig.getMainFunctionId())) { AnomalyFunctionDTO mainAnomalyFunction = anomalyFunctionDAO.findById(classificationConfig.getMainFunctionId()); if (mainAnomalyFunction.getIsActive()) { involvedAnomalyFunctions.add(mainAnomalyFunction); } else { LOG.info("Main anomaly function is not activated. Classification job: {} is skipped.", classificationConfig); return; } } // Check the latest detection time among all anomaly functions in this classification config long minDetectionEndTime = Long.MAX_VALUE; for (AnomalyFunctionDTO anomalyFunctionDTO : involvedAnomalyFunctions) { JobDTO job = jobDAO.findLatestCompletedDetectionJobByFunctionId(anomalyFunctionDTO.getId()); if (job != null) { minDetectionEndTime = Math.min(minDetectionEndTime, job.getWindowEndTime()); } else { minDetectionEndTime = Long.MAX_VALUE; LOG.warn("Could not find most recent executed job for function {}; aborting job for classification config {}", anomalyFunctionDTO.getId(), classificationConfig); break; } } if (minDetectionEndTime == Long.MAX_VALUE) { return; } long startTime; long endTime = minDetectionEndTime; // Get the most recent classification job JobDTO classificationJobDTO = jobDAO.findLatestCompletedGroupingJobById(classificationConfig.getId()); // Continue from previous completed classification job if (classificationJobDTO != null) { long recentClassificationEndTime = classificationJobDTO.getWindowEndTime(); // skip this job if we have processed the latest available window if (minDetectionEndTime > recentClassificationEndTime) { startTime = recentClassificationEndTime; } else { LOG.info("Skipped grouping for id {}; Info: minDetectionEndTime among all anomaly functions -- {}, lastGroupingEndTime -- {}", classificationJobDTO.getId(), recentClassificationEndTime, minDetectionEndTime); return; } } else { // otherwise, we look back for a certain range of time startTime = minDetectionEndTime - maxLookbackLength; } // create classification job GroupingJobContext jobContext = new GroupingJobContext(); jobContext.setWindowStartTime(startTime); jobContext.setWindowEndTime(endTime); jobContext.setConfigDTO(classificationConfig); GroupingJobRunner jobRunner = new GroupingJobRunner(jobContext); jobRunner.run(); } }