package com.linkedin.thirdeye.rootcause.impl; import com.linkedin.thirdeye.client.DAORegistry; import com.linkedin.thirdeye.datalayer.bao.EntityToEntityMappingManager; import com.linkedin.thirdeye.datalayer.dto.EntityToEntityMappingDTO; import com.linkedin.thirdeye.rootcause.Entity; import com.linkedin.thirdeye.rootcause.Pipeline; import com.linkedin.thirdeye.rootcause.PipelineContext; import com.linkedin.thirdeye.rootcause.PipelineResult; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The EntityMappingPipeline is a generic implementation for emitting Entities based on * association with incoming Entities. For example, it may be used to generate "similar" metrics * for each incoming MetricEntity based on a per-defined mapping of Entity URNs to other URNs. */ public class EntityMappingPipeline extends Pipeline { private static final Logger LOG = LoggerFactory.getLogger(EntityMappingPipeline.class); public static final String PROP_MAPPING_TYPE = "mappingType"; public static final String PROP_IS_REWRITER = "isRewriter"; public static final String PROP_MATCH_PREFIX = "matchPrefix"; public static final String PROP_IS_REWRITER_DEFAULT = "false"; public static final String PROP_MATCH_PREFIX_DEFAULT = "false"; private final EntityToEntityMappingManager entityDAO; private final String mappingType; private final boolean isRewriter; private final boolean matchPrefix; /** * Constructor for dependency injection * * @param outputName pipeline output name * @param inputNames input pipeline names * @param entityDAO entity mapping DAO * @param mappingType entity mapping type * @param isRewriter enable rewriter mode (pass-through for entities without mapping) * @param matchPrefix match on URN prefix rather than entire URN */ public EntityMappingPipeline(String outputName, Set<String> inputNames, EntityToEntityMappingManager entityDAO, String mappingType, boolean isRewriter, boolean matchPrefix) { super(outputName, inputNames); this.entityDAO = entityDAO; this.mappingType = mappingType; this.isRewriter = isRewriter; this.matchPrefix = matchPrefix; } /** * Alternate constructor for use by PipelineLoader * * @param outputName pipeline output name * @param inputNames input pipeline names * @param properties configuration properties ({@code PROP_MAPPING_TYPE}, {@code PROP_IS_REWRITER=false}, {@code PROP_MATCH_PREFIX=false}) */ public EntityMappingPipeline(String outputName, Set<String> inputNames, Map<String, String> properties) throws IOException { super(outputName, inputNames); if(!properties.containsKey(PROP_MAPPING_TYPE)) throw new IllegalArgumentException(String.format("Property '%s' required, but not found", PROP_MAPPING_TYPE)); String mappingTypeProp = properties.get(PROP_MAPPING_TYPE); String isRewriterProp = String.valueOf(PROP_IS_REWRITER_DEFAULT); if(properties.containsKey(PROP_IS_REWRITER)) isRewriterProp = properties.get(PROP_IS_REWRITER); String matchPrefixProp = String.valueOf(PROP_MATCH_PREFIX_DEFAULT); if(properties.containsKey(PROP_MATCH_PREFIX)) matchPrefixProp = properties.get(PROP_MATCH_PREFIX); this.entityDAO = DAORegistry.getInstance().getEntityToEntityMappingDAO(); this.mappingType = mappingTypeProp; this.isRewriter = Boolean.parseBoolean(isRewriterProp); this.matchPrefix = Boolean.parseBoolean(matchPrefixProp); } @Override public PipelineResult run(PipelineContext context) { Set<Entity> entities = context.filter(Entity.class); Map<String, EntityToEntityMappingDTO> mappings = toMap(this.entityDAO.findByMappingType(this.mappingType)); Set<Entity> output = new HashSet<>(); for(Entity entity : entities) { try { Entity newEntity = replace(entity, mappings); if(newEntity != null) { if(LOG.isDebugEnabled()) { LOG.debug("Mapping {} [{}] {} to {} [{}] {}", entity.getScore(), entity.getClass().getSimpleName(), entity.getUrn(), newEntity.getScore(), newEntity.getClass().getSimpleName(), newEntity.getUrn()); } output.add(newEntity); } } catch (Exception ex) { LOG.warn("Exception while mapping entity '{}'. Skipping.", entity.getUrn(), ex); } } return new PipelineResult(context, output); } private Entity replace(Entity entity, Map<String, EntityToEntityMappingDTO> mappings) { return this.matchPrefix ? replacePrefix(entity, mappings) : replaceFull(entity, mappings); } private Entity replacePrefix(Entity entity, Map<String, EntityToEntityMappingDTO> mappings) { EntityToEntityMappingDTO mapping = findPrefix(entity, mappings); if(mapping == null) return handleNoMapping(entity); String postfix = entity.getUrn().substring(mapping.getFromURN().length()); String toURN = mapping.getToURN() + postfix; return EntityUtils.parseURN(toURN, entity.getScore() * mapping.getScore()); } private Entity replaceFull(Entity entity, Map<String, EntityToEntityMappingDTO> mappings) { EntityToEntityMappingDTO mapping = mappings.get(entity.getUrn()); if(mapping == null) return handleNoMapping(entity); return EntityUtils.parseURN(mapping.getToURN(), entity.getScore() * mapping.getScore()); } private Entity handleNoMapping(Entity e) { if(this.isRewriter) return e; return null; } private EntityToEntityMappingDTO findPrefix(Entity entity, Map<String, EntityToEntityMappingDTO> mappings) { for(Map.Entry<String, EntityToEntityMappingDTO> mapping : mappings.entrySet()) { if(entity.getUrn().startsWith(mapping.getKey())) return mapping.getValue(); } return null; } private static Map<String, EntityToEntityMappingDTO> toMap(Iterable<EntityToEntityMappingDTO> mappings) { Map<String, EntityToEntityMappingDTO> m = new HashMap<>(); for(EntityToEntityMappingDTO dto : mappings) { m.put(dto.getFromURN(), dto); } return m; } }