package com.linkedin.thirdeye.rootcause.impl;
import com.linkedin.thirdeye.anomaly.events.EventDataProviderManager;
import com.linkedin.thirdeye.anomaly.events.EventFilter;
import com.linkedin.thirdeye.anomaly.events.EventType;
import com.linkedin.thirdeye.client.DAORegistry;
import com.linkedin.thirdeye.datalayer.bao.MetricConfigManager;
import com.linkedin.thirdeye.datalayer.dto.EventDTO;
import com.linkedin.thirdeye.datalayer.dto.MetricConfigDTO;
import com.linkedin.thirdeye.rootcause.Pipeline;
import com.linkedin.thirdeye.rootcause.PipelineContext;
import com.linkedin.thirdeye.rootcause.PipelineResult;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Pipeline for identifying anomaly events based on their associated metric
* names. The pipeline identifies metric entities in the search context and then invokes the
* event provider manager to fetch any matching events. It then scores events based on their
* time distance from the end of the search time window (closer is better).
*/
public class AnomalyEventsPipeline extends Pipeline {
private static final Logger LOG = LoggerFactory.getLogger(AnomalyEventsPipeline.class);
private final EventDataProviderManager manager;
private final MetricConfigManager metricDAO;
/**
* Constructor for dependency injection
*
* @param outputName pipeline output name
* @param inputNames input pipeline names
* @param manager event data provider manager
* @param metricDAO metric config DAO
*/
public AnomalyEventsPipeline(String outputName, Set<String> inputNames, EventDataProviderManager manager, MetricConfigManager metricDAO) {
super(outputName, inputNames);
this.manager = manager;
this.metricDAO = metricDAO;
}
/**
* Alternate constructor for use by PipelineLoader
*
* @param outputName pipeline output name
* @param inputNames input pipeline names
* @param ignore configuration properties (none)
*/
public AnomalyEventsPipeline(String outputName, Set<String> inputNames, Map<String, String> ignore) {
super(outputName, inputNames);
this.manager = EventDataProviderManager.getInstance();
this.metricDAO = DAORegistry.getInstance().getMetricConfigDAO();
}
@Override
public PipelineResult run(PipelineContext context) {
Set<MetricEntity> metrics = context.filter(MetricEntity.class);
TimeRangeEntity current = TimeRangeEntity.getContextCurrent(context);
Set<EventEntity> entities = new HashSet<>();
for(MetricEntity me : metrics) {
MetricConfigDTO metricDTO = this.metricDAO.findById(me.getId());
if(metricDTO == null) {
LOG.warn("Could not resolve metric id {}. Skipping.", me.getId());
continue;
}
EventFilter filter = new EventFilter();
filter.setEventType(EventType.HISTORICAL_ANOMALY.toString());
filter.setMetricName(metricDTO.getName());
for(EventDTO eventDTO : manager.getEvents(filter)) {
double score = getScore(current, eventDTO);
entities.add(EventEntity.fromDTO(score, eventDTO));
}
}
return new PipelineResult(context, entities);
}
/**
* Compute event score based on distance to the end of the current time window. Closer is better.
*
* @param current current time range
* @param dto event dto
* @return event entity score
*/
private double getScore(TimeRangeEntity current, EventDTO dto) {
long duration = current.getEnd() - current.getStart();
long distance = dto.getEndTime() - current.getEnd();
return 1.0 - distance / (double)duration;
}
}