This is an automated email from the ASF dual-hosted git repository. jihao pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
The following commit(s) were added to refs/heads/master by this push: new aa6d48f [TE] add threshold-based anomaly labeler (#5972) aa6d48f is described below commit aa6d48f183a6e7fd7c3c09e71bd165926da561aa Author: Vincent Chen <jianc...@linkedin.com> AuthorDate: Tue Sep 8 17:05:16 2020 -0700 [TE] add threshold-based anomaly labeler (#5972) This PR is the first part of adding an anomaly labeler for better alerting ThirdEye users. It adds a labeling phase and a threshold-based labeler. The next PR is to add the logic of translating YAML to JSON config. --- .../AnomalySeverity.java} | 28 +++- .../datalayer/dto/MergedAnomalyResultDTO.java | 11 ++ .../datalayer/pojo/MergedAnomalyResultBean.java | 15 ++ .../thirdeye/detection/DetectionPipeline.java | 22 +++ .../detection/algorithm/DimensionWrapper.java | 22 +-- .../algorithm/LegacyAlertFilterWrapper.java | 16 +- .../algorithm/LegacyDimensionWrapper.java | 24 +-- .../detection/algorithm/LegacyMergeWrapper.java | 17 +- .../thirdeye/detection/algorithm/MergeWrapper.java | 15 +- .../detection/annotation/DetectionTag.java | 3 +- .../components/ThresholdSeverityLabeler.java | 88 ++++++++++ .../SeverityThresholdLabelerSpec.java} | 40 ++++- .../components/Labeler.java} | 19 ++- .../detection/wrapper/AnomalyFilterWrapper.java | 17 +- ...lterWrapper.java => AnomalyLabelerWrapper.java} | 79 +++------ .../thirdeye/detection/wrapper/GrouperWrapper.java | 15 +- .../components/ThresholdRuleAnomalyFilterTest.java | 2 - .../components/ThresholdSeverityLabelerTest.java | 181 +++++++++++++++++++++ 18 files changed, 426 insertions(+), 188 deletions(-) diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/anomaly/AnomalySeverity.java similarity index 64% copy from thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java copy to thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/anomaly/AnomalySeverity.java index c30120d..16b545a 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/anomaly/AnomalySeverity.java @@ -17,12 +17,26 @@ * under the License. */ -package org.apache.pinot.thirdeye.detection.annotation; +package org.apache.pinot.thirdeye.anomaly; -public enum DetectionTag { - ALGORITHM_DETECTION, - RULE_DETECTION, - ALGORITHM_FILTER, - RULE_FILTER, - GROUPER +/** + * The severity of anomaly. + */ +public enum AnomalySeverity { + // the order of definition follows the severity from highest to lowest + CRITICAL ("critical"), + HIGH ("high"), + MEDIUM ("medium"), + LOW ("low"), + DEFAULT ("default"); + + private String severity; + + AnomalySeverity(String severity) { + this.severity = severity; + } + + public String getLabel() { + return severity; + } } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dto/MergedAnomalyResultDTO.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dto/MergedAnomalyResultDTO.java index 415a252..69b08c5 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dto/MergedAnomalyResultDTO.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dto/MergedAnomalyResultDTO.java @@ -44,6 +44,9 @@ public class MergedAnomalyResultDTO extends MergedAnomalyResultBean implements A private Set<MergedAnomalyResultDTO> children = new HashSet<>(); + // flag to be set when severity changes but not to be persisted + private boolean renotify = false; + public MergedAnomalyResultDTO() { setCreatedTime(System.currentTimeMillis()); } @@ -107,6 +110,14 @@ public class MergedAnomalyResultDTO extends MergedAnomalyResultBean implements A this.children = children; } + public boolean isRenotify() { + return renotify; + } + + public void setRenotify(boolean renotify) { + this.renotify = renotify; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/pojo/MergedAnomalyResultBean.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/pojo/MergedAnomalyResultBean.java index 834356d..cf9236c 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/pojo/MergedAnomalyResultBean.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/pojo/MergedAnomalyResultBean.java @@ -20,6 +20,7 @@ package org.apache.pinot.thirdeye.datalayer.pojo; import java.io.Serializable; +import org.apache.pinot.thirdeye.anomaly.AnomalySeverity; import org.apache.pinot.thirdeye.anomaly.AnomalyType; import org.apache.pinot.thirdeye.common.dimension.DimensionMap; import org.apache.pinot.thirdeye.constant.AnomalyResultSource; @@ -60,6 +61,8 @@ public class MergedAnomalyResultBean extends AbstractBean implements Comparable< private Set<Long> childIds; // ids of the anomalies this anomaly merged from private boolean isChild; private AnomalyType type; + private AnomalySeverity severityLabel; + public Set<Long> getChildIds() { return childIds; @@ -264,6 +267,18 @@ public class MergedAnomalyResultBean extends AbstractBean implements Comparable< this.type = type; } + public void setSeverityLabel(AnomalySeverity severityLabel) { + this.severityLabel = severityLabel; + } + + public AnomalySeverity getSeverityLabel() { + // default severity level is debug + if (severityLabel == null) { + return AnomalySeverity.DEFAULT; + } + return severityLabel; + } + @Override public int hashCode() { return Objects.hash(getId(), startTime, endTime, collection, metric, dimensions, score, impactToGlobal, avgBaselineVal, avgCurrentVal, anomalyResultSource, metricUrn, detectionConfigId, childIds, isChild); diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionPipeline.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionPipeline.java index d10a7f4..b6521da 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionPipeline.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionPipeline.java @@ -19,7 +19,9 @@ package org.apache.pinot.thirdeye.detection; +import com.google.common.base.Preconditions; import com.google.common.collect.Multimap; +import java.util.HashMap; import org.apache.pinot.thirdeye.common.dimension.DimensionMap; import org.apache.pinot.thirdeye.dataframe.BooleanSeries; import org.apache.pinot.thirdeye.dataframe.DataFrame; @@ -244,6 +246,26 @@ public abstract class DetectionPipeline { return anomalies; } + /** + * Helper to initialize and run the next level wrapper + * @param nestedProps nested properties + * @return intermediate result of a detection pipeline + * @throws Exception + */ + protected DetectionPipelineResult runNested( + Map<String, Object> nestedProps, final long startTime, final long endTime) throws Exception { + Preconditions.checkArgument(nestedProps.containsKey(PROP_CLASS_NAME), "Nested missing " + PROP_CLASS_NAME); + Map<String, Object> properties = new HashMap<>(nestedProps); + DetectionConfigDTO nestedConfig = new DetectionConfigDTO(); + nestedConfig.setId(this.config.getId()); + nestedConfig.setName(this.config.getName()); + nestedConfig.setDescription(this.config.getDescription()); + nestedConfig.setComponents(this.config.getComponents()); + nestedConfig.setProperties(properties); + DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, startTime, endTime); + return pipeline.run(); + } + // TODO anomaly should support multimap private DimensionMap toFilterMap(Multimap<String, String> filters) { DimensionMap map = new DimensionMap(); diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/DimensionWrapper.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/DimensionWrapper.java index a798cdb..77bdaa4 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/DimensionWrapper.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/DimensionWrapper.java @@ -332,7 +332,8 @@ public class DimensionWrapper extends DetectionPipeline { for (Map<String, Object> properties : this.nestedProperties) { DetectionPipelineResult intermediate; try { - intermediate = this.runNested(metric, properties); + properties.put(this.nestedMetricUrnKey, metric.getUrn()); + intermediate = this.runNested(properties, this.startTime, this.endTime); } catch (Exception e) { LOG.warn("[DetectionConfigID{}] detecting anomalies for window {} to {} failed for metric urn {}.", this.config.getId(), this.start, this.end, metric.getUrn(), e); @@ -446,23 +447,4 @@ public class DimensionWrapper extends DetectionPipeline { } return false; } - - protected DetectionPipelineResult runNested(MetricEntity metric, Map<String, Object> template) throws Exception { - Preconditions.checkArgument(template.containsKey(PROP_CLASS_NAME), "Nested missing " + PROP_CLASS_NAME); - - Map<String, Object> properties = new HashMap<>(template); - - properties.put(this.nestedMetricUrnKey, metric.getUrn()); - - DetectionConfigDTO nestedConfig = new DetectionConfigDTO(); - nestedConfig.setId(this.config.getId()); - nestedConfig.setName(this.config.getName()); - nestedConfig.setDescription(this.config.getDescription()); - nestedConfig.setProperties(properties); - nestedConfig.setComponents(this.config.getComponents()); - - DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, this.startTime, this.endTime); - - return pipeline.run(); - } } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyAlertFilterWrapper.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyAlertFilterWrapper.java index c100306..776271c 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyAlertFilterWrapper.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyAlertFilterWrapper.java @@ -95,26 +95,14 @@ public class LegacyAlertFilterWrapper extends DetectionPipeline { @Override public DetectionPipelineResult run() throws Exception { List<MergedAnomalyResultDTO> candidates = new ArrayList<>(); - for (Map<String, Object> propertiesRaw : this.nestedProperties) { - Map<String, Object> properties = new HashMap<>(propertiesRaw); - DetectionConfigDTO nestedConfig = new DetectionConfigDTO(); - - Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), "Nested missing " + PROP_CLASS_NAME); - + for (Map<String, Object> properties : this.nestedProperties) { if (!properties.containsKey(PROP_SPEC)) { properties.put(PROP_SPEC, this.anomalyFunctionSpecs); } if (!properties.containsKey(PROP_ANOMALY_FUNCTION_CLASS)) { properties.put(PROP_ANOMALY_FUNCTION_CLASS, this.config.getProperties().get(PROP_ANOMALY_FUNCTION_CLASS)); } - nestedConfig.setId(this.config.getId()); - nestedConfig.setName(this.config.getName()); - nestedConfig.setDescription(this.config.getDescription()); - nestedConfig.setProperties(properties); - - DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, this.startTime - this.alertFilterLookBack, this.endTime); - - DetectionPipelineResult intermediate = pipeline.run(); + DetectionPipelineResult intermediate = this.runNested(properties, this.startTime - this.alertFilterLookBack, this.endTime); candidates.addAll(intermediate.getAnomalies()); } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyDimensionWrapper.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyDimensionWrapper.java index 6ba74bb..31b75f7 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyDimensionWrapper.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyDimensionWrapper.java @@ -83,25 +83,15 @@ public class LegacyDimensionWrapper extends DimensionWrapper { } @Override - protected DetectionPipelineResult runNested(MetricEntity metric, Map<String, Object> template) throws Exception { - Map<String, Object> properties = new HashMap<>(template); - - properties.put(this.nestedMetricUrnKey, metric.getUrn()); - if (!properties.containsKey(PROP_SPEC)) { - properties.put(PROP_SPEC, this.anomalyFunctionSpecs); + protected DetectionPipelineResult runNested( + Map<String, Object> nestedProps, final long startTime, final long endTime) throws Exception { + if (!nestedProps.containsKey(PROP_SPEC)) { + nestedProps.put(PROP_SPEC, this.anomalyFunctionSpecs); } - if (!properties.containsKey(PROP_ANOMALY_FUNCTION_CLASS)) { - properties.put(PROP_ANOMALY_FUNCTION_CLASS, this.anomalyFunctionClassName); + if (!nestedProps.containsKey(PROP_ANOMALY_FUNCTION_CLASS)) { + nestedProps.put(PROP_ANOMALY_FUNCTION_CLASS, this.anomalyFunctionClassName); } - DetectionConfigDTO nestedConfig = new DetectionConfigDTO(); - nestedConfig.setId(this.config.getId()); - nestedConfig.setName(this.config.getName()); - nestedConfig.setDescription(this.config.getDescription()); - nestedConfig.setProperties(properties); - - DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, this.startTime, this.endTime); - - return pipeline.run(); + return super.runNested(nestedProps, startTime, endTime); } private static DetectionConfigDTO augmentConfig(DetectionConfigDTO config) { diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyMergeWrapper.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyMergeWrapper.java index 7d3435e..51ea009 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyMergeWrapper.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyMergeWrapper.java @@ -143,27 +143,14 @@ public class LegacyMergeWrapper extends DetectionPipeline { // generate anomalies List<MergedAnomalyResultDTO> generated = new ArrayList<>(); - for (Map<String, Object> propertiesRaw : this.nestedProperties) { - Map<String, Object> properties = new HashMap<>(propertiesRaw); - DetectionConfigDTO nestedConfig = new DetectionConfigDTO(); - - Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), "Nested missing " + PROP_CLASS_NAME); - + for (Map<String, Object> properties : this.nestedProperties) { if (!properties.containsKey(PROP_SPEC)) { properties.put(PROP_SPEC, this.anomalyFunctionSpecs); } if (!properties.containsKey(PROP_ANOMALY_FUNCTION_CLASS)) { properties.put(PROP_ANOMALY_FUNCTION_CLASS, this.anomalyFunctionClassName); } - nestedConfig.setId(this.config.getId()); - nestedConfig.setName(this.config.getName()); - nestedConfig.setDescription(this.config.getDescription()); - nestedConfig.setProperties(properties); - - DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, this.startTime, this.endTime); - - DetectionPipelineResult intermediate = pipeline.run(); - + DetectionPipelineResult intermediate = this.runNested(properties, startTime, endTime); generated.addAll(intermediate.getAnomalies()); } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/MergeWrapper.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/MergeWrapper.java index f9a55f3..ee67927 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/MergeWrapper.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/MergeWrapper.java @@ -123,20 +123,8 @@ public class MergeWrapper extends DetectionPipeline { int i = 0; Set<Long> lastTimeStamps = new HashSet<>(); for (Map<String, Object> properties : this.nestedProperties) { - DetectionConfigDTO nestedConfig = new DetectionConfigDTO(); - - Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), "Nested missing " + PROP_CLASS_NAME); - - nestedConfig.setId(this.config.getId()); - nestedConfig.setName(this.config.getName()); - nestedConfig.setDescription(this.config.getDescription()); - nestedConfig.setProperties(properties); - nestedConfig.setComponents(this.config.getComponents()); - DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, this.startTime, this.endTime); - - DetectionPipelineResult intermediate = pipeline.run(); + DetectionPipelineResult intermediate = this.runNested(properties, this.startTime, this.endTime); lastTimeStamps.add(intermediate.getLastTimestamp()); - generated.addAll(intermediate.getAnomalies()); predictionResults.addAll(intermediate.getPredictions()); evaluations.addAll(intermediate.getEvaluations()); @@ -376,6 +364,7 @@ public class MergeWrapper extends DetectionPipeline { to.setWeight(from.getWeight()); to.setProperties(from.getProperties()); to.setType(from.getType()); + to.setSeverityLabel(from.getSeverityLabel()); return to; } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java index c30120d..cef608a 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java @@ -24,5 +24,6 @@ public enum DetectionTag { RULE_DETECTION, ALGORITHM_FILTER, RULE_FILTER, - GROUPER + GROUPER, + LABELER } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabeler.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabeler.java new file mode 100644 index 0000000..ff04015 --- /dev/null +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabeler.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.thirdeye.detection.components; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.apache.pinot.thirdeye.anomaly.AnomalySeverity; +import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO; +import org.apache.pinot.thirdeye.detection.InputDataFetcher; +import org.apache.pinot.thirdeye.detection.annotation.Components; +import org.apache.pinot.thirdeye.detection.annotation.DetectionTag; +import org.apache.pinot.thirdeye.detection.spec.SeverityThresholdLabelerSpec; +import org.apache.pinot.thirdeye.detection.spi.components.Labeler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.pinot.thirdeye.detection.spec.SeverityThresholdLabelerSpec.Threshold; + +/** + * Threshold-based severity labeler, which labels anomalies with severity based on deviation from baseline and duration + * of the anomalies. It tries to label anomalies from highest to lowest if deviation or duration exceeds the threshold + */ +@Components(title = "ThresholdSeverityLabeler", type = "THRESHOLD_SEVERITY_LABELER", + tags = {DetectionTag.LABELER}, description = "An threshold-based labeler for anomaly severity") +public class ThresholdSeverityLabeler implements Labeler<SeverityThresholdLabelerSpec> { + private final static Logger LOG = LoggerFactory.getLogger(ThresholdSeverityLabeler.class); + // severity map ordered by priority from top to bottom + private TreeMap<AnomalySeverity, Threshold> severityMap; + + @Override + public void label(List<MergedAnomalyResultDTO> anomalies) { + for (MergedAnomalyResultDTO anomaly : anomalies) { + double currVal = anomaly.getAvgCurrentVal(); + double baseVal = anomaly.getAvgBaselineVal(); + if (Double.isNaN(currVal) || Double.isNaN(baseVal)) { + LOG.warn("Unable to label anomaly for detection {} from {} to {}, so skipping labeling...", + anomaly.getDetectionConfigId(), anomaly.getStartTime(), anomaly.getEndTime()); + continue; + } + double deviation = Math.abs(currVal - baseVal) / baseVal; + long duration = anomaly.getEndTime() - anomaly.getStartTime(); + for (Map.Entry<AnomalySeverity, Threshold> entry : severityMap.entrySet()) { + if (deviation >= entry.getValue().change || duration >= entry.getValue().duration) { + if (anomaly.getSeverityLabel() != entry.getKey()) { + // find the severity from highest to lowest + if (anomaly.getId() != null && anomaly.getSeverityLabel().compareTo(entry.getKey()) > 0) { + // only set renotify if the anomaly exists and its severity gets higher + anomaly.setRenotify(true); + } + anomaly.setSeverityLabel(entry.getKey()); + break; + } + } + } + } + } + + @Override + public void init(SeverityThresholdLabelerSpec spec, InputDataFetcher dataFetcher) { + this.severityMap = new TreeMap<>(); + for (String key : spec.getSeverity().keySet()) { + try { + AnomalySeverity severity = AnomalySeverity.valueOf(key); + this.severityMap.put(severity, spec.getSeverity().get(key)); + } catch (IllegalArgumentException e) { + LOG.error("Cannot find valid anomaly severity, so ignoring...", e); + } + } + } +} diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spec/SeverityThresholdLabelerSpec.java similarity index 50% copy from thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java copy to thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spec/SeverityThresholdLabelerSpec.java index c30120d..8390580 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spec/SeverityThresholdLabelerSpec.java @@ -17,12 +17,36 @@ * under the License. */ -package org.apache.pinot.thirdeye.detection.annotation; - -public enum DetectionTag { - ALGORITHM_DETECTION, - RULE_DETECTION, - ALGORITHM_FILTER, - RULE_FILTER, - GROUPER +package org.apache.pinot.thirdeye.detection.spec; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.Map; + + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SeverityThresholdLabelerSpec extends AbstractSpec{ + private Map<String, Threshold> severity; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Threshold { + public double change = Double.MAX_VALUE; + public long duration = Long.MAX_VALUE; + + public Threshold() { + + } + + public Threshold(double change, long duration) { + this.change = change; + this.duration = duration; + } + } + + public Map<String, Threshold> getSeverity() { + return severity; + } + + public void setSeverity(Map<String, Threshold> severity) { + this.severity = severity; + } } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spi/components/Labeler.java similarity index 65% copy from thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java copy to thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spi/components/Labeler.java index c30120d..61541a4 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spi/components/Labeler.java @@ -17,12 +17,17 @@ * under the License. */ -package org.apache.pinot.thirdeye.detection.annotation; +package org.apache.pinot.thirdeye.detection.spi.components; -public enum DetectionTag { - ALGORITHM_DETECTION, - RULE_DETECTION, - ALGORITHM_FILTER, - RULE_FILTER, - GROUPER +import java.util.List; +import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO; +import org.apache.pinot.thirdeye.detection.spec.AbstractSpec; + + +public interface Labeler <T extends AbstractSpec> extends BaseComponent<T> { + /** + * add or modify labels of anomalies in place + * @param anomalies + */ + void label(List<MergedAnomalyResultDTO> anomalies); } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java index 2b8eb1f..565d277 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java @@ -46,7 +46,6 @@ import org.apache.commons.collections4.MapUtils; */ public class AnomalyFilterWrapper extends DetectionPipeline { private static final String PROP_NESTED = "nested"; - private static final String PROP_CLASS_NAME = "className"; private static final String PROP_METRIC_URN = "metricUrn"; private static final String PROP_FILTER = "filter"; @@ -81,21 +80,7 @@ public class AnomalyFilterWrapper extends DetectionPipeline { Set<Long> lastTimeStamps = new HashSet<>(); for (Map<String, Object> properties : this.nestedProperties) { - DetectionConfigDTO nestedConfig = new DetectionConfigDTO(); - - Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), "Nested missing " + PROP_CLASS_NAME); - HashMap<String, Object> nestedProp = new HashMap<>(properties); - if (this.metricUrn != null){ - nestedProp.put(PROP_METRIC_URN, this.metricUrn); - } - nestedConfig.setId(this.config.getId()); - nestedConfig.setName(this.config.getName()); - nestedConfig.setDescription(this.config.getDescription()); - nestedConfig.setProperties(nestedProp); - nestedConfig.setComponents(this.config.getComponents()); - DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, this.startTime, this.endTime); - - DetectionPipelineResult intermediate = pipeline.run(); + DetectionPipelineResult intermediate = this.runNested(properties, this.startTime, this.endTime); lastTimeStamps.add(intermediate.getLastTimestamp()); diagnostics.putAll(intermediate.getDiagnostics()); evaluations.addAll(intermediate.getEvaluations()); diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyLabelerWrapper.java similarity index 51% copy from thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java copy to thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyLabelerWrapper.java index 2b8eb1f..5d59e9b 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyLabelerWrapper.java @@ -20,10 +20,13 @@ package org.apache.pinot.thirdeye.detection.wrapper; import com.google.common.base.Preconditions; -import com.google.common.collect.Collections2; -import java.util.Collection; +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.collections4.MapUtils; import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO; import org.apache.pinot.thirdeye.datalayer.dto.EvaluationDTO; import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO; @@ -33,80 +36,48 @@ import org.apache.pinot.thirdeye.detection.DetectionPipeline; import org.apache.pinot.thirdeye.detection.DetectionPipelineResult; import org.apache.pinot.thirdeye.detection.DetectionUtils; import org.apache.pinot.thirdeye.detection.PredictionResult; -import org.apache.pinot.thirdeye.detection.spi.components.AnomalyFilter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.commons.collections4.MapUtils; - +import org.apache.pinot.thirdeye.detection.spi.components.Labeler; /** - * This anomaly filter wrapper runs the anomaly filter component to filter anomalies generated by detector based on the filter implementation. + * This anomaly labeler wrapper runs the anomaly labeler component to label anomalies generated by detector based on the labeler implementation. */ -public class AnomalyFilterWrapper extends DetectionPipeline { +public class AnomalyLabelerWrapper extends DetectionPipeline { private static final String PROP_NESTED = "nested"; - private static final String PROP_CLASS_NAME = "className"; - private static final String PROP_METRIC_URN = "metricUrn"; - private static final String PROP_FILTER = "filter"; + private static final String PROP_LABELER = "labeler"; private final List<Map<String, Object>> nestedProperties; - private final AnomalyFilter anomalyFilter; - private String metricUrn; + private String labelerName; + private Labeler labeler; - public AnomalyFilterWrapper(DataProvider provider, DetectionConfigDTO config, long startTime, long endTime) { + public AnomalyLabelerWrapper(DataProvider provider, DetectionConfigDTO config, long startTime, long endTime) { super(provider, config, startTime, endTime); Map<String, Object> properties = config.getProperties(); this.nestedProperties = ConfigUtils.getList(properties.get(PROP_NESTED)); - Preconditions.checkArgument(this.config.getProperties().containsKey(PROP_FILTER)); - String detectorReferenceKey = DetectionUtils.getComponentKey(MapUtils.getString(config.getProperties(), PROP_FILTER)); - Preconditions.checkArgument(this.config.getComponents().containsKey(detectorReferenceKey)); - this.anomalyFilter = (AnomalyFilter) this.config.getComponents().get(detectorReferenceKey); - - this.metricUrn = MapUtils.getString(properties, PROP_METRIC_URN); + Preconditions.checkArgument(this.config.getProperties().containsKey(PROP_LABELER)); + this.labelerName = DetectionUtils.getComponentKey(MapUtils.getString(config.getProperties(), PROP_LABELER)); + Preconditions.checkArgument(this.config.getComponents().containsKey(this.labelerName)); + this.labeler = (Labeler) this.config.getComponents().get(this.labelerName); } - /** - * Runs the nested pipelines and calls the isQualified method in the anomaly filter stage to check if an anomaly passes the filter. - * @return the detection pipeline result - * @throws Exception - */ @Override - public final DetectionPipelineResult run() throws Exception { - List<MergedAnomalyResultDTO> candidates = new ArrayList<>(); - List<PredictionResult> predictionResults = new ArrayList<>(); + public DetectionPipelineResult run() throws Exception { + List<MergedAnomalyResultDTO> anomalies = new ArrayList<>(); Map<String, Object> diagnostics = new HashMap<>(); + List<PredictionResult> predictionResults = new ArrayList<>(); List<EvaluationDTO> evaluations = new ArrayList<>(); Set<Long> lastTimeStamps = new HashSet<>(); for (Map<String, Object> properties : this.nestedProperties) { - DetectionConfigDTO nestedConfig = new DetectionConfigDTO(); - - Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), "Nested missing " + PROP_CLASS_NAME); - HashMap<String, Object> nestedProp = new HashMap<>(properties); - if (this.metricUrn != null){ - nestedProp.put(PROP_METRIC_URN, this.metricUrn); - } - nestedConfig.setId(this.config.getId()); - nestedConfig.setName(this.config.getName()); - nestedConfig.setDescription(this.config.getDescription()); - nestedConfig.setProperties(nestedProp); - nestedConfig.setComponents(this.config.getComponents()); - DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, this.startTime, this.endTime); - - DetectionPipelineResult intermediate = pipeline.run(); + DetectionPipelineResult intermediate = this.runNested(properties, this.startTime, this.endTime); lastTimeStamps.add(intermediate.getLastTimestamp()); - diagnostics.putAll(intermediate.getDiagnostics()); - evaluations.addAll(intermediate.getEvaluations()); predictionResults.addAll(intermediate.getPredictions()); - candidates.addAll(intermediate.getAnomalies()); + evaluations.addAll(intermediate.getEvaluations()); + diagnostics.putAll(intermediate.getDiagnostics()); + anomalies.addAll(intermediate.getAnomalies()); } - - Collection<MergedAnomalyResultDTO> anomalies = - Collections2.filter(candidates, mergedAnomaly -> mergedAnomaly != null && !mergedAnomaly.isChild() && anomalyFilter.isQualified(mergedAnomaly)); - - return new DetectionPipelineResult(new ArrayList<>(anomalies), DetectionUtils.consolidateNestedLastTimeStamps(lastTimeStamps), + this.labeler.label(anomalies); + return new DetectionPipelineResult(anomalies, DetectionUtils.consolidateNestedLastTimeStamps(lastTimeStamps), predictionResults, evaluations).setDiagnostics(diagnostics); } } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/GrouperWrapper.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/GrouperWrapper.java index b786cfa..6a13bbf 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/GrouperWrapper.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/GrouperWrapper.java @@ -49,7 +49,6 @@ import static org.apache.pinot.thirdeye.detection.yaml.translator.DetectionConfi */ public class GrouperWrapper extends DetectionPipeline { private static final String PROP_NESTED = "nested"; - private static final String PROP_CLASS_NAME = "className"; private static final String PROP_GROUPER = "grouper"; public static final String PROP_DETECTOR_COMPONENT_NAME = "detectorComponentName"; @@ -88,20 +87,8 @@ public class GrouperWrapper extends DetectionPipeline { Set<Long> lastTimeStamps = new HashSet<>(); for (Map<String, Object> properties : this.nestedProperties) { - DetectionConfigDTO nestedConfig = new DetectionConfigDTO(); - - Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), "Nested missing " + PROP_CLASS_NAME); - - nestedConfig.setId(this.config.getId()); - nestedConfig.setName(this.config.getName()); - nestedConfig.setDescription(this.config.getDescription()); - nestedConfig.setProperties(properties); - nestedConfig.setComponents(this.config.getComponents()); - DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, this.startTime, this.endTime); - - DetectionPipelineResult intermediate = pipeline.run(); + DetectionPipelineResult intermediate = this.runNested(properties, this.startTime, this.endTime); lastTimeStamps.add(intermediate.getLastTimestamp()); - predictionResults.addAll(intermediate.getPredictions()); evaluations.addAll(intermediate.getEvaluations()); diagnostics.putAll(intermediate.getDiagnostics()); diff --git a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdRuleAnomalyFilterTest.java b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdRuleAnomalyFilterTest.java index ebee72f..63e244e 100644 --- a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdRuleAnomalyFilterTest.java +++ b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdRuleAnomalyFilterTest.java @@ -24,10 +24,8 @@ import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO; import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO; import org.apache.pinot.thirdeye.datalayer.dto.MetricConfigDTO; import org.apache.pinot.thirdeye.detection.DataProvider; -import org.apache.pinot.thirdeye.detection.DefaultInputDataFetcher; import org.apache.pinot.thirdeye.detection.DetectionPipelineResult; import org.apache.pinot.thirdeye.detection.DetectionTestUtils; -import org.apache.pinot.thirdeye.detection.InputDataFetcher; import org.apache.pinot.thirdeye.detection.MockDataProvider; import org.apache.pinot.thirdeye.detection.MockPipeline; import org.apache.pinot.thirdeye.detection.MockPipelineLoader; diff --git a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabelerTest.java b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabelerTest.java new file mode 100644 index 0000000..56c13d4 --- /dev/null +++ b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabelerTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2014-2018 LinkedIn Corp. (pinot-c...@linkedin.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.pinot.thirdeye.detection.components; + +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.apache.pinot.thirdeye.anomaly.AnomalySeverity; +import org.apache.pinot.thirdeye.dataframe.DataFrame; +import org.apache.pinot.thirdeye.dataframe.util.MetricSlice; +import org.apache.pinot.thirdeye.datalayer.dto.DatasetConfigDTO; +import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO; +import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO; +import org.apache.pinot.thirdeye.datalayer.dto.MetricConfigDTO; +import org.apache.pinot.thirdeye.detection.DataProvider; +import org.apache.pinot.thirdeye.detection.DetectionPipelineResult; +import org.apache.pinot.thirdeye.detection.DetectionTestUtils; +import org.apache.pinot.thirdeye.detection.MockDataProvider; +import org.apache.pinot.thirdeye.detection.MockPipeline; +import org.apache.pinot.thirdeye.detection.MockPipelineLoader; +import org.apache.pinot.thirdeye.detection.MockPipelineOutput; +import org.apache.pinot.thirdeye.detection.wrapper.AnomalyLabelerWrapper; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.apache.pinot.thirdeye.dataframe.util.DataFrameUtils.*; +import static org.apache.pinot.thirdeye.detection.spec.SeverityThresholdLabelerSpec.Threshold; + +public class ThresholdSeverityLabelerTest { + private static final String METRIC_URN = "thirdeye:metric:123"; + private static final long CONFIG_ID = 125L; + + private List<MergedAnomalyResultDTO> anomalies; + private MockPipelineLoader loader; + private List<MockPipeline> runs; + private DataProvider testDataProvider; + private Map<String, Object> properties; + private DetectionConfigDTO config; + private Map<String, Object> specs; + private AnomalyLabelerWrapper thresholdSeverityLabeler; + + @BeforeMethod + public void beforeMethod(){ + Map<MetricSlice, DataFrame> aggregates = new HashMap<>(); + aggregates.put(MetricSlice.from(123L, 1000L, 2000L), new DataFrame().addSeries(COL_VALUE, 1200)); + aggregates.put(MetricSlice.from(123L, 2000L, 3000L), new DataFrame().addSeries(COL_VALUE, 1600)); + aggregates.put(MetricSlice.from(123L, 3000L, 4000L), new DataFrame().addSeries(COL_VALUE, 4800)); + aggregates.put(MetricSlice.from(123L, 4000L, 6000L), new DataFrame().addSeries(COL_VALUE, 2500)); + + MetricConfigDTO metricConfigDTO = new MetricConfigDTO(); + metricConfigDTO.setId(123L); + metricConfigDTO.setName("thirdeye-test"); + metricConfigDTO.setDataset("thirdeye-test-dataset"); + + DatasetConfigDTO datasetConfigDTO = new DatasetConfigDTO(); + datasetConfigDTO.setId(124L); + datasetConfigDTO.setDataset("thirdeye-test-dataset"); + datasetConfigDTO.setTimeDuration(2); + datasetConfigDTO.setTimeUnit(TimeUnit.MILLISECONDS); + datasetConfigDTO.setTimezone("UTC"); + + this.config = new DetectionConfigDTO(); + this.config.setId(CONFIG_ID); + this.properties = new HashMap<>(); + this.properties.put("nested", Collections.singletonList(Collections.singletonMap("className", "dummy"))); + this.properties.put("labeler", "$test_labeler"); + this.specs = new HashMap<>(); + this.specs.put("className", ThresholdSeverityLabeler.class.getName()); + + this.config.setComponentSpecs(ImmutableMap.of("test_labeler", this.specs)); + this.config.setProperties(this.properties); + + this.anomalies = + Arrays.asList(DetectionTestUtils.makeAnomaly(1000L, 2000L, METRIC_URN, 1200, 1000), + DetectionTestUtils.makeAnomaly(2000L, 3000, METRIC_URN, 1700, 2000), + DetectionTestUtils.makeAnomaly(3000L, 4000L, METRIC_URN, 4800, 5000), + DetectionTestUtils.makeAnomaly(4000L, 6000L, METRIC_URN, 2500, 3000)); + this.runs = new ArrayList<>(); + this.loader = new MockPipelineLoader(this.runs, + Collections.singletonList(new MockPipelineOutput(this.anomalies, 6000L))); + this.testDataProvider = new MockDataProvider().setLoader(this.loader) + .setMetrics(Collections.singletonList(metricConfigDTO)) + .setDatasets(Collections.singletonList(datasetConfigDTO)) + .setAggregates(aggregates); + } + + + @Test + public void testLabeling() throws Exception { + Map<String, Object> severityMap = new HashMap<>(); + severityMap.put(AnomalySeverity.CRITICAL.toString(), new Threshold(0.2, 3000)); + severityMap.put(AnomalySeverity.HIGH.toString(), new Threshold(0.15, 2000)); + severityMap.put(AnomalySeverity.MEDIUM.toString(), new Threshold(0.12, 1500)); + this.specs.put("severity", severityMap); + this.thresholdSeverityLabeler = new AnomalyLabelerWrapper(this.testDataProvider, this.config, 1000L, 6000L); + DetectionPipelineResult result = this.thresholdSeverityLabeler.run(); + List<MergedAnomalyResultDTO> anomalies = result.getAnomalies(); + Assert.assertEquals(anomalies.size(), 4); + Assert.assertEquals(anomalies.get(0).getSeverityLabel(), AnomalySeverity.CRITICAL); + Assert.assertEquals(anomalies.get(1).getSeverityLabel(), AnomalySeverity.HIGH); + Assert.assertEquals(anomalies.get(2).getSeverityLabel(), AnomalySeverity.DEFAULT); + Assert.assertEquals(anomalies.get(3).getSeverityLabel(), AnomalySeverity.HIGH); + } + + @Test + public void testLabelingSingleThreshold() throws Exception { + Map<String, Object> severityMap = new HashMap<>(); + Threshold singleThreshold = new Threshold(); + singleThreshold.duration = 2000L; + severityMap.put(AnomalySeverity.CRITICAL.toString(), singleThreshold); + severityMap.put(AnomalySeverity.HIGH.toString(), new Threshold(0.15, 2000)); + this.specs.put("severity", severityMap); + this.thresholdSeverityLabeler = new AnomalyLabelerWrapper(this.testDataProvider, this.config, 1000L, 6000L); + DetectionPipelineResult result = this.thresholdSeverityLabeler.run(); + List<MergedAnomalyResultDTO> anomalies = result.getAnomalies(); + Assert.assertEquals(anomalies.size(), 4); + Assert.assertEquals(anomalies.get(0).getSeverityLabel(), AnomalySeverity.HIGH); + Assert.assertEquals(anomalies.get(1).getSeverityLabel(), AnomalySeverity.HIGH); + Assert.assertEquals(anomalies.get(2).getSeverityLabel(), AnomalySeverity.DEFAULT); + Assert.assertEquals(anomalies.get(3).getSeverityLabel(), AnomalySeverity.CRITICAL); + } + + @Test + public void testLabelingHigherSeverity() throws Exception { + Map<String, Object> severityMap = new HashMap<>(); + severityMap.put(AnomalySeverity.CRITICAL.toString(), new Threshold(0.2, 3000)); + severityMap.put(AnomalySeverity.HIGH.toString(), new Threshold(0.15, 2000)); + severityMap.put(AnomalySeverity.MEDIUM.toString(), new Threshold(0.12, 1500)); + this.specs.put("severity", severityMap); + MergedAnomalyResultDTO anomaly = DetectionTestUtils.makeAnomaly(4000L, 6000L, METRIC_URN, 2400, 3000); + anomaly.setSeverityLabel(AnomalySeverity.HIGH); + anomaly.setId(125L); + this.anomalies.set(this.anomalies.size() - 1, anomaly); + this.thresholdSeverityLabeler = new AnomalyLabelerWrapper(this.testDataProvider, this.config, 1000L, 6000L); + DetectionPipelineResult result = this.thresholdSeverityLabeler.run(); + List<MergedAnomalyResultDTO> anomalies = result.getAnomalies(); + Assert.assertEquals(anomalies.size(), 4); + Assert.assertEquals(anomalies.get(3).getSeverityLabel(), AnomalySeverity.CRITICAL); + Assert.assertTrue(anomalies.get(3).isRenotify()); + + } + + @Test + public void testLabelingLowerSeverity() throws Exception { + Map<String, Object> severityMap = new HashMap<>(); + severityMap.put(AnomalySeverity.CRITICAL.toString(), new Threshold(0.2, 3000)); + severityMap.put(AnomalySeverity.HIGH.toString(), new Threshold(0.15, 2000)); + severityMap.put(AnomalySeverity.MEDIUM.toString(), new Threshold(0.10, 1500)); + this.specs.put("severity", severityMap); + MergedAnomalyResultDTO anomaly = DetectionTestUtils.makeAnomaly(4000L, 6000L, METRIC_URN, 2700, 3000); + anomaly.setSeverityLabel(AnomalySeverity.HIGH); + anomaly.setId(125L); + this.anomalies.set(this.anomalies.size() - 1, anomaly); + this.thresholdSeverityLabeler = new AnomalyLabelerWrapper(this.testDataProvider, this.config, 1000L, 6000L); + DetectionPipelineResult result = this.thresholdSeverityLabeler.run(); + List<MergedAnomalyResultDTO> anomalies = result.getAnomalies(); + Assert.assertEquals(anomalies.size(), 4); + Assert.assertEquals(anomalies.get(3).getSeverityLabel(), AnomalySeverity.MEDIUM); + Assert.assertFalse(anomalies.get(3).isRenotify()); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org