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.datalayer.dto.EventDTO; import com.linkedin.thirdeye.rootcause.Pipeline; import com.linkedin.thirdeye.rootcause.PipelineContext; import com.linkedin.thirdeye.rootcause.PipelineResult; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * HolidayEventsPipeline produces EventEntities associated with holidays within the current * TimeRange. It matches holidays based on incoming DimensionEntities (e.g. from contribution * analysis) and scores them based on the number of matching DimensionEntities. * Holiday pipeline will add a buffer of 2 days to the time range provided */ public class HolidayEventsPipeline extends Pipeline { private static final int HOLIDAY_DAYS_BUFFER = 2; private static final Logger LOG = LoggerFactory.getLogger(HolidayEventsPipeline.class); private final EventDataProviderManager eventDataProvider; /** * Constructor for dependency injection * * @param outputName pipeline output name * @param inputNames input pipeline names * @param eventDataProvider event data provider manager */ public HolidayEventsPipeline(String outputName, Set<String> inputNames, EventDataProviderManager eventDataProvider) { super(outputName, inputNames); this.eventDataProvider = eventDataProvider; } /** * Alternate constructor for PipelineLoader * * @param outputName pipeline output name * @param inputNames input pipeline names * @param ignore configuration properties (none) */ public HolidayEventsPipeline(String outputName, Set<String> inputNames, Map<String, String> ignore) { super(outputName, inputNames); this.eventDataProvider = EventDataProviderManager.getInstance(); } @Override public PipelineResult run(PipelineContext context) { TimeRangeEntity current = TimeRangeEntity.getContextCurrent(context); TimeRangeEntity baseline = TimeRangeEntity.getContextBaseline(context); Set<DimensionEntity> dimensionEntities = context.filter(DimensionEntity.class); Map<String, DimensionEntity> urn2entity = EntityUtils.mapEntityURNs(dimensionEntities); List<EventDTO> events = getHolidayEvents(current, dimensionEntities); events.addAll(getHolidayEvents(baseline, dimensionEntities)); Set<EventEntity> entities = new HashSet<>(); for(EventDTO ev : events) { double dimensionScore = makeDimensionScore(urn2entity, ev.getTargetDimensionMap()); EventEntity entity = EventEntity.fromDTO(dimensionScore, ev); LOG.debug("{}: dimension={}, filter={}", entity.getUrn(), dimensionScore, ev.getTargetDimensionMap()); entities.add(entity); } return new PipelineResult(context, entities); } private List<EventDTO> getHolidayEvents(TimeRangeEntity timerangeEntity, Set<DimensionEntity> dimensionEntities) { long start = new DateTime(timerangeEntity.getStart()).minusDays(HOLIDAY_DAYS_BUFFER).getMillis(); long end = timerangeEntity.getEnd(); EventFilter filter = new EventFilter(); filter.setEventType(EventType.HOLIDAY.toString()); filter.setStartTime(start); filter.setEndTime(end); Map<String, List<String>> filterMap = new HashMap<>(); if (CollectionUtils.isNotEmpty(dimensionEntities)) { for (DimensionEntity dimensionEntity : dimensionEntities) { String dimensionName = dimensionEntity.getName(); String dimensionValue = dimensionEntity.getValue(); if (!filterMap.containsKey(dimensionName)) { filterMap.put(dimensionName, new ArrayList<String>()); } filterMap.get(dimensionName).add(dimensionValue); } } filter.setTargetDimensionMap(filterMap); return eventDataProvider.getEvents(filter); } static double makeDimensionScore(Map<String, DimensionEntity> urn2entity, Map<String, List<String>> dimensionFilterMap) { double sum = 0.0; Set<String> urns = filter2urns(dimensionFilterMap); for(String urn : urns) { if(urn2entity.containsKey(urn)) { sum += urn2entity.get(urn).getScore(); } } return sum; } static Set<String> filter2urns(Map<String, List<String>> dimensionFilterMap) { Set<String> urns = new HashSet<>(); for(Map.Entry<String, List<String>> e : dimensionFilterMap.entrySet()) { for(String val : e.getValue()) { urns.add(DimensionEntity.TYPE.formatURN(e.getKey(), val.toLowerCase())); } } return urns; } }