package com.linkedin.thirdeye.dashboard.resources.v2; import com.linkedin.thirdeye.client.DAORegistry; import com.linkedin.thirdeye.dashboard.resources.v2.pojo.RootCauseEntity; import com.linkedin.thirdeye.dashboard.resources.v2.rootcause.DefaultEntityFormatter; import com.linkedin.thirdeye.dashboard.resources.v2.rootcause.DimensionEntityFormatter; import com.linkedin.thirdeye.dashboard.resources.v2.rootcause.EventEntityFormatter; import com.linkedin.thirdeye.dashboard.resources.v2.rootcause.MetricEntityFormatter; import com.linkedin.thirdeye.dashboard.resources.v2.rootcause.ServiceEntityFormatter; import com.linkedin.thirdeye.rootcause.Entity; import com.linkedin.thirdeye.rootcause.RCAFramework; import com.linkedin.thirdeye.rootcause.RCAFrameworkExecutionResult; import com.linkedin.thirdeye.rootcause.impl.EntityUtils; import com.linkedin.thirdeye.rootcause.impl.TimeRangeEntity; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Path(value = "/rootcause") @Produces(MediaType.APPLICATION_JSON) public class RootCauseResource { private static final Logger LOG = LoggerFactory.getLogger(RootCauseResource.class); private static final DateTimeFormatter ISO8601 = ISODateTimeFormat.basicDateTimeNoMillis(); private final List<RootCauseEntityFormatter> formatters; private static final long HOUR_IN_MS = 60 * 60 * 1000; private static final long DAY_IN_MS = 24 * HOUR_IN_MS; private final RCAFramework rca; public RootCauseResource(RCAFramework rca, List<RootCauseEntityFormatter> formatters) { this.rca = rca; this.formatters = formatters; } @GET @Path("/query") public List<RootCauseEntity> queryRootCause( @QueryParam("current") Long current, @QueryParam("currentDate") String currentDate, @QueryParam("baseline") Long baseline, @QueryParam("baselineDate") String baselineDate, @QueryParam("windowSize") Long windowSize, @QueryParam("windowSizeInHours") Long windowSizeInHours, @QueryParam("windowSizeInDays") Long windowSizeInDays, @QueryParam("urn") List<String> urns, @QueryParam("pipeline") @DefaultValue("OUTPUT") String pipeline) throws Exception { // input validation if((current == null && currentDate == null) || (current != null && currentDate != null)) throw new IllegalArgumentException("Must provide either currentDate or current"); if((baseline == null && baselineDate == null) || (baseline != null && baselineDate != null)) throw new IllegalArgumentException("Must provide baselineDate or baseline"); // TODO check exclusive use if(windowSize == null && windowSizeInHours == null && windowSizeInDays == null) throw new IllegalArgumentException("Must provide either windowSize, windowSizeInHours, or windowSizeInDays"); if(currentDate != null) current = ISO8601.parseMillis(currentDate); if(baselineDate != null) baseline = ISO8601.parseMillis(baselineDate); if(windowSizeInHours != null) windowSize = windowSizeInHours * HOUR_IN_MS; if(windowSizeInDays != null) windowSize = windowSizeInDays * DAY_IN_MS; // format input Set<Entity> input = new HashSet<>(); input.add(TimeRangeEntity.fromRange(1.0, TimeRangeEntity.TYPE_CURRENT, current - windowSize, current)); input.add(TimeRangeEntity.fromRange(1.0, TimeRangeEntity.TYPE_BASELINE, baseline - windowSize, baseline)); for(String urn : urns) { input.add(EntityUtils.parseURN(urn, 1.0)); } // run root-cause analysis RCAFrameworkExecutionResult result = this.rca.run(input); // format output if(!result.getPipelineResults().containsKey(pipeline)) throw new IllegalArgumentException(String.format("Could not find pipeline '%s'", pipeline)); List<RootCauseEntity> output = new ArrayList<>(); for(Entity e : result.getPipelineResults().get(pipeline).getEntities()) { output.add(applyFormatters(e)); } Collections.sort(output, new Comparator<RootCauseEntity>() { @Override public int compare(RootCauseEntity o1, RootCauseEntity o2) { return -1 * Double.compare(o1.getScore(), o2.getScore()); } }); return output; } RootCauseEntity applyFormatters(Entity e) { for(RootCauseEntityFormatter formatter : this.formatters) { if(formatter.applies(e)) { try { return formatter.format(e); } catch (Exception ex) { LOG.warn("Error applying formatter '{}'. Skipping.", ex); } } } throw new IllegalArgumentException(String.format("No formatter for Entity '%s'", e.getUrn())); } }