package com.linkedin.thirdeye.anomaly.utils;
import com.linkedin.thirdeye.anomalydetection.context.AnomalyFeedback;
import com.linkedin.thirdeye.constant.AnomalyFeedbackType;
import com.linkedin.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.collections.CollectionUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AnomalyUtils {
private static final Logger LOG = LoggerFactory.getLogger(AnomalyUtils.class);
/**
* Logs the known anomalies whose window overlaps with the given window, whose range is defined by windowStart
* and windowEnd.
*
* Reason to log the overlapped anomalies: During anomaly detection, the know anomalies are supposedly used to remove
* abnormal baseline values but not current values. This method provides a check before sending the known anomalies to
* anomaly detection functions.
*
* @param windowStart the inclusive start time of the window
* @param windowEnd the exclusive end time of the window
* @param knownAnomalies the known anomalies
*/
public static void logAnomaliesOverlapWithWindow(DateTime windowStart, DateTime windowEnd,
List<MergedAnomalyResultDTO> knownAnomalies) {
if (CollectionUtils.isEmpty(knownAnomalies) || windowEnd.compareTo(windowStart) <= 0) {
return;
}
List<MergedAnomalyResultDTO> overlappedAnomalies = new ArrayList<>();
for (MergedAnomalyResultDTO knownAnomaly : knownAnomalies) {
if (knownAnomaly.getStartTime() <= windowEnd.getMillis() && knownAnomaly.getEndTime() >= windowStart.getMillis()) {
overlappedAnomalies.add(knownAnomaly);
}
}
if (overlappedAnomalies.size() > 0) {
StringBuffer sb = new StringBuffer();
String separator = "";
for (MergedAnomalyResultDTO overlappedAnomaly : overlappedAnomalies) {
sb.append(separator).append(overlappedAnomaly.getStartTime()).append("--").append(overlappedAnomaly.getEndTime());
separator = ", ";
}
LOG.warn("{} merged anomalies overlap with this window {} -- {}. Anomalies: {}", overlappedAnomalies.size(),
windowStart, windowEnd, sb.toString());
}
}
/**
* This function checks if the input list of merged anomalies has at least one positive label.
* It is a helper for alert filter auto tuning
* @param mergedAnomalyResultDTOS
* @return true if the list of merged anomalies has at least one positive label, false otherwise
*/
public static Boolean checkHasLabels(List<MergedAnomalyResultDTO> mergedAnomalyResultDTOS){
for(MergedAnomalyResultDTO anomaly: mergedAnomalyResultDTOS){
AnomalyFeedback feedback = anomaly.getFeedback();
if (feedback != null){
return true;
}
}
return false;
}
/**
* Safely and quitely shutdown executor service.
*
* @param executorService the executor service to be shutdown.
* @param ownerClass the class that owns the executor service.
*/
public static void safelyShutdownExecutionService(ExecutorService executorService, Class ownerClass) {
if (executorService == null) {
return;
}
executorService.shutdown(); // Prevent new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {
executorService.shutdownNow(); // Force terminate all currently executing tasks
// Wait for tasks to be cancelled
if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {
if (ownerClass != null) {
LOG.error("Failed to terminate thread pool for class {}", ownerClass.getSimpleName());
} else {
LOG.error("Failed to terminate thread pool: {}", executorService);
}
}
}
} catch (InterruptedException e) { // If current thread is interrupted
executorService.shutdownNow(); // Interrupt all currently executing tasks for the last time
Thread.currentThread().interrupt();
}
}
/**
* This is a subclass describing anomalies features as training data for alert filter
*/
public static class MetaDataNode {
public double windowSize;
public double severity;
public String startTimeISO;
public String endTimeISO;
public String functionName;
public String feedback;
public long anomalyId;
public MetaDataNode(MergedAnomalyResultDTO anomaly){
this.windowSize = 1. * (anomaly.getEndTime() - anomaly.getStartTime()) / 3600000L;
this.severity = Math.abs(anomaly.getWeight());
this.startTimeISO = new Timestamp(anomaly.getStartTime()).toString();
this.endTimeISO = new Timestamp(anomaly.getEndTime()).toString();
this.functionName = anomaly.getFunction().getFunctionName();
this.feedback = (anomaly.getFeedback() == null)? "null" : String.valueOf(anomaly.getFeedback().getFeedbackType());
this.anomalyId = anomaly.getId();
}
}
}