package com.linkedin.thirdeye.anomaly.detection; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.linkedin.thirdeye.client.DAORegistry; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.linkedin.thirdeye.anomaly.job.JobConstants.JobStatus; import com.linkedin.thirdeye.anomaly.task.TaskConstants.TaskStatus; import com.linkedin.thirdeye.anomaly.task.TaskConstants.TaskType; import com.linkedin.thirdeye.anomaly.task.TaskGenerator; import com.linkedin.thirdeye.api.TimeGranularity; import com.linkedin.thirdeye.api.TimeSpec; import com.linkedin.thirdeye.dashboard.Utils; import com.linkedin.thirdeye.datalayer.dto.AnomalyFunctionDTO; import com.linkedin.thirdeye.datalayer.dto.DatasetConfigDTO; import com.linkedin.thirdeye.datalayer.dto.JobDTO; import com.linkedin.thirdeye.datalayer.dto.TaskDTO; import com.linkedin.thirdeye.util.ThirdEyeUtils; public class DetectionJobRunner { private static final Logger LOG = LoggerFactory.getLogger(DetectionJobRunner.class); private static final DAORegistry DAO_REGISTRY = DAORegistry.getInstance(); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private TaskGenerator taskGenerator; long functionId; public DetectionJobRunner() { taskGenerator = new TaskGenerator(); } /** * Creates anomaly detection job and tasks, depending on the information in context * @param detectionJobContext * @return */ public Long run(DetectionJobContext detectionJobContext) { functionId = detectionJobContext.getAnomalyFunctionId(); AnomalyFunctionDTO anomalyFunction = DAO_REGISTRY.getAnomalyFunctionDAO().findById(functionId); List<DateTime> monitoringWindowStartTimes = new ArrayList<>(); List<DateTime> monitoringWindowEndTimes = new ArrayList<>(); List<Long> startTimes = detectionJobContext.getStartTimes(); List<Long> endTimes = detectionJobContext.getEndTimes(); for (Long startTime : startTimes) { DateTime monitoringWindowStartTime = new DateTime(startTime); monitoringWindowStartTime = alignTimestampsToDataTimezone(monitoringWindowStartTime, anomalyFunction.getCollection()); monitoringWindowStartTimes.add(monitoringWindowStartTime); } for (Long endTime : endTimes) { DateTime monitoringWindowEndTime = new DateTime(endTime); monitoringWindowEndTime = alignTimestampsToDataTimezone(monitoringWindowEndTime, anomalyFunction.getCollection()); monitoringWindowEndTimes.add(monitoringWindowEndTime); } // write to anomaly_jobs Long jobExecutionId = createJob(detectionJobContext.getJobName(), monitoringWindowStartTimes.get(0), monitoringWindowEndTimes.get(0)); detectionJobContext.setJobExecutionId(jobExecutionId); // write to anomaly_tasks List<Long> taskIds = createTasks(detectionJobContext, monitoringWindowStartTimes, monitoringWindowEndTimes); return jobExecutionId; } private DateTime alignTimestampsToDataTimezone(DateTime inputDateTime, String collection) { try { DatasetConfigDTO datasetConfig = DAO_REGISTRY.getDatasetConfigDAO().findByDataset(collection); TimeSpec timespec = ThirdEyeUtils.getTimeSpecFromDatasetConfig(datasetConfig); TimeGranularity dataGranularity = datasetConfig.bucketTimeGranularity(); String timeFormat = timespec.getFormat(); if (dataGranularity.getUnit().equals(TimeUnit.DAYS)) { DateTimeZone dataTimeZone = Utils.getDataTimeZone(collection); DateTimeFormatter inputDataDateTimeFormatter = DateTimeFormat.forPattern(timeFormat).withZone(dataTimeZone); long inputMillis = inputDateTime.getMillis(); String inputDateTimeString = inputDataDateTimeFormatter.print(inputMillis); long timeZoneOffsetMillis = inputDataDateTimeFormatter.parseMillis(inputDateTimeString); inputDateTime = new DateTime(timeZoneOffsetMillis); } } catch (Exception e) { LOG.error("Exception in aligning timestamp to data time zone", e); } return inputDateTime; } private long createJob(String jobName, DateTime monitoringWindowStartTime, DateTime monitoringWindowEndTime) { Long jobExecutionId = null; try { JobDTO jobSpec = new JobDTO(); jobSpec.setJobName(jobName); jobSpec.setWindowStartTime(monitoringWindowStartTime.getMillis()); jobSpec.setWindowEndTime(monitoringWindowEndTime.getMillis()); jobSpec.setScheduleStartTime(System.currentTimeMillis()); jobSpec.setStatus(JobStatus.SCHEDULED); jobSpec.setTaskType(TaskType.ANOMALY_DETECTION); jobSpec.setAnomalyFunctionId(functionId); jobExecutionId = DAO_REGISTRY.getJobDAO().save(jobSpec); LOG.info("Created anomalyJobSpec {} with jobExecutionId {}", jobSpec, jobExecutionId); } catch (Exception e) { LOG.error("Exception in creating detection job", e); } return jobExecutionId; } private List<Long> createTasks(DetectionJobContext detectionJobContext, List<DateTime> monitoringWindowStartTimes, List<DateTime> monitoringWindowEndTimes) { List<Long> taskIds = new ArrayList<>(); try { List<DetectionTaskInfo> tasks = taskGenerator.createDetectionTasks(detectionJobContext, monitoringWindowStartTimes, monitoringWindowEndTimes); for (DetectionTaskInfo taskInfo : tasks) { String taskInfoJson = null; try { taskInfoJson = OBJECT_MAPPER.writeValueAsString(taskInfo); } catch (JsonProcessingException e) { LOG.error("Exception when converting DetectionTaskInfo {} to jsonString", taskInfo, e); } TaskDTO taskSpec = new TaskDTO(); taskSpec.setTaskType(TaskType.ANOMALY_DETECTION); taskSpec.setJobName(detectionJobContext.getJobName()); taskSpec.setStatus(TaskStatus.WAITING); taskSpec.setStartTime(System.currentTimeMillis()); taskSpec.setTaskInfo(taskInfoJson); taskSpec.setJobId(detectionJobContext.getJobExecutionId()); long taskId = DAO_REGISTRY.getTaskDAO().save(taskSpec); taskIds.add(taskId); LOG.info("Created anomalyTask {} with taskId {}", taskSpec, taskId); } } catch (Exception e) { LOG.error("Exception in creating detection tasks", e); } return taskIds; } }