|
@@ -6,19 +6,20 @@
|
|
|
*/
|
|
|
package org.elasticsearch.xpack.core.ml.job.config;
|
|
|
|
|
|
-import org.elasticsearch.common.xcontent.ParseField;
|
|
|
+import org.elasticsearch.Version;
|
|
|
import org.elasticsearch.common.Strings;
|
|
|
import org.elasticsearch.common.io.stream.StreamInput;
|
|
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
|
import org.elasticsearch.common.io.stream.Writeable;
|
|
|
-import org.elasticsearch.core.TimeValue;
|
|
|
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
|
|
import org.elasticsearch.common.xcontent.ObjectParser;
|
|
|
+import org.elasticsearch.common.xcontent.ParseField;
|
|
|
import org.elasticsearch.common.xcontent.ToXContentObject;
|
|
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
|
+import org.elasticsearch.core.TimeValue;
|
|
|
+import org.elasticsearch.xpack.core.common.time.TimeUtils;
|
|
|
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
|
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
|
|
-import org.elasticsearch.xpack.core.common.time.TimeUtils;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
import java.util.ArrayList;
|
|
@@ -42,7 +43,7 @@ import java.util.stream.Collectors;
|
|
|
* <p>
|
|
|
* The configuration can contain multiple detectors, a new anomaly detector will
|
|
|
* be created for each detector configuration. The fields
|
|
|
- * <code>bucketSpan, summaryCountFieldName and categorizationFieldName</code>
|
|
|
+ * <code>bucketSpan, modelPruneWindow, summaryCountFieldName and categorizationFieldName</code>
|
|
|
* apply to all detectors.
|
|
|
* <p>
|
|
|
* If a value has not been set it will be <code>null</code>
|
|
@@ -55,6 +56,7 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
*/
|
|
|
public static final ParseField ANALYSIS_CONFIG = new ParseField("analysis_config");
|
|
|
public static final ParseField BUCKET_SPAN = new ParseField("bucket_span");
|
|
|
+ public static final ParseField MODEL_PRUNE_WINDOW = new ParseField("model_prune_window");
|
|
|
public static final ParseField CATEGORIZATION_FIELD_NAME = new ParseField("categorization_field_name");
|
|
|
public static final ParseField CATEGORIZATION_FILTERS = new ParseField("categorization_filters");
|
|
|
public static final ParseField CATEGORIZATION_ANALYZER = CategorizationAnalyzerConfig.CATEGORIZATION_ANALYZER;
|
|
@@ -72,6 +74,9 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
public static final ConstructingObjectParser<AnalysisConfig.Builder, Void> LENIENT_PARSER = createParser(true);
|
|
|
public static final ConstructingObjectParser<AnalysisConfig.Builder, Void> STRICT_PARSER = createParser(false);
|
|
|
|
|
|
+ // The minimum number of buckets considered acceptable for the model_prune_window field
|
|
|
+ public static final long MINIMUM_MODEL_PRUNE_WINDOW_BUCKETS = 2;
|
|
|
+
|
|
|
@SuppressWarnings("unchecked")
|
|
|
private static ConstructingObjectParser<AnalysisConfig.Builder, Void> createParser(boolean ignoreUnknownFields) {
|
|
|
ConstructingObjectParser<AnalysisConfig.Builder, Void> parser = new ConstructingObjectParser<>(ANALYSIS_CONFIG.getPreferredName(),
|
|
@@ -96,6 +101,8 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
parser.declareString(Builder::setSummaryCountFieldName, SUMMARY_COUNT_FIELD_NAME);
|
|
|
parser.declareStringArray(Builder::setInfluencers, INFLUENCERS);
|
|
|
parser.declareBoolean(Builder::setMultivariateByFields, MULTIVARIATE_BY_FIELDS);
|
|
|
+ parser.declareString((builder, val) ->
|
|
|
+ builder.setModelPruneWindow(TimeValue.parseTimeValue(val, MODEL_PRUNE_WINDOW.getPreferredName())), MODEL_PRUNE_WINDOW);
|
|
|
|
|
|
return parser;
|
|
|
}
|
|
@@ -113,11 +120,14 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
private final List<Detector> detectors;
|
|
|
private final List<String> influencers;
|
|
|
private final Boolean multivariateByFields;
|
|
|
+ private final TimeValue modelPruneWindow;
|
|
|
+
|
|
|
|
|
|
private AnalysisConfig(TimeValue bucketSpan, String categorizationFieldName, List<String> categorizationFilters,
|
|
|
CategorizationAnalyzerConfig categorizationAnalyzerConfig,
|
|
|
PerPartitionCategorizationConfig perPartitionCategorizationConfig, TimeValue latency,
|
|
|
- String summaryCountFieldName, List<Detector> detectors, List<String> influencers, Boolean multivariateByFields) {
|
|
|
+ String summaryCountFieldName, List<Detector> detectors, List<String> influencers, Boolean multivariateByFields,
|
|
|
+ TimeValue modelPruneWindow) {
|
|
|
this.detectors = detectors;
|
|
|
this.bucketSpan = bucketSpan;
|
|
|
this.latency = latency;
|
|
@@ -128,6 +138,7 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
this.summaryCountFieldName = summaryCountFieldName;
|
|
|
this.influencers = Collections.unmodifiableList(influencers);
|
|
|
this.multivariateByFields = multivariateByFields;
|
|
|
+ this.modelPruneWindow = modelPruneWindow;
|
|
|
}
|
|
|
|
|
|
public AnalysisConfig(StreamInput in) throws IOException {
|
|
@@ -142,6 +153,11 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
influencers = Collections.unmodifiableList(in.readStringList());
|
|
|
|
|
|
multivariateByFields = in.readOptionalBoolean();
|
|
|
+ if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
|
|
|
+ modelPruneWindow = in.readOptionalTimeValue();
|
|
|
+ } else {
|
|
|
+ modelPruneWindow = null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -162,6 +178,10 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
out.writeStringCollection(influencers);
|
|
|
|
|
|
out.writeOptionalBoolean(multivariateByFields);
|
|
|
+
|
|
|
+ if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
|
|
|
+ out.writeOptionalTimeValue(modelPruneWindow);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -261,6 +281,10 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
return multivariateByFields;
|
|
|
}
|
|
|
|
|
|
+ public TimeValue getModelPruneWindow() {
|
|
|
+ return modelPruneWindow;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Return the set of fields required by the analysis.
|
|
|
* These are the influencer fields, metric field, partition field,
|
|
@@ -360,6 +384,10 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
if (multivariateByFields != null) {
|
|
|
builder.field(MULTIVARIATE_BY_FIELDS.getPreferredName(), multivariateByFields);
|
|
|
}
|
|
|
+ if (modelPruneWindow != null) {
|
|
|
+ builder.field(MODEL_PRUNE_WINDOW.getPreferredName(), modelPruneWindow.getStringRep());
|
|
|
+ }
|
|
|
+
|
|
|
builder.endObject();
|
|
|
return builder;
|
|
|
}
|
|
@@ -378,14 +406,16 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
Objects.equals(summaryCountFieldName, that.summaryCountFieldName) &&
|
|
|
Objects.equals(detectors, that.detectors) &&
|
|
|
Objects.equals(influencers, that.influencers) &&
|
|
|
- Objects.equals(multivariateByFields, that.multivariateByFields);
|
|
|
+ Objects.equals(multivariateByFields, that.multivariateByFields) &&
|
|
|
+ Objects.equals(modelPruneWindow, that.modelPruneWindow);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public int hashCode() {
|
|
|
return Objects.hash(
|
|
|
- bucketSpan, categorizationFieldName, categorizationFilters, categorizationAnalyzerConfig, perPartitionCategorizationConfig,
|
|
|
- latency, summaryCountFieldName, detectors, influencers, multivariateByFields);
|
|
|
+ bucketSpan, categorizationFieldName, categorizationFilters, categorizationAnalyzerConfig,
|
|
|
+ perPartitionCategorizationConfig, latency, summaryCountFieldName, detectors, influencers, multivariateByFields,
|
|
|
+ modelPruneWindow);
|
|
|
}
|
|
|
|
|
|
public static class Builder {
|
|
@@ -402,6 +432,7 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
private String summaryCountFieldName;
|
|
|
private List<String> influencers = new ArrayList<>();
|
|
|
private Boolean multivariateByFields;
|
|
|
+ private TimeValue modelPruneWindow;
|
|
|
|
|
|
public Builder(List<Detector> detectors) {
|
|
|
setDetectors(detectors);
|
|
@@ -419,6 +450,7 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
this.summaryCountFieldName = analysisConfig.summaryCountFieldName;
|
|
|
this.influencers = new ArrayList<>(analysisConfig.influencers);
|
|
|
this.multivariateByFields = analysisConfig.multivariateByFields;
|
|
|
+ this.modelPruneWindow = analysisConfig.modelPruneWindow;
|
|
|
}
|
|
|
|
|
|
public Builder setDetectors(List<Detector> detectors) {
|
|
@@ -489,10 +521,15 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
+ public Builder setModelPruneWindow(TimeValue modelPruneWindow) {
|
|
|
+ this.modelPruneWindow = modelPruneWindow;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Checks the configuration is valid
|
|
|
* <ol>
|
|
|
- * <li>Check that if non-null BucketSpan and Latency are >= 0</li>
|
|
|
+ * <li>Check that if non-null BucketSpan, ModelPruneWindow and Latency are >= 0</li>
|
|
|
* <li>Check that if non-null Latency is <= MAX_LATENCY</li>
|
|
|
* <li>Check there is at least one detector configured</li>
|
|
|
* <li>Check all the detectors are configured correctly</li>
|
|
@@ -503,6 +540,9 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
*/
|
|
|
public AnalysisConfig build() {
|
|
|
TimeUtils.checkPositiveMultiple(bucketSpan, TimeUnit.SECONDS, BUCKET_SPAN);
|
|
|
+
|
|
|
+ verifyModelPruneWindow();
|
|
|
+
|
|
|
if (latency != null) {
|
|
|
TimeUtils.checkNonNegativeMultiple(latency, TimeUnit.SECONDS, LATENCY);
|
|
|
}
|
|
@@ -521,7 +561,28 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
|
|
|
verifyNoInconsistentNestedFieldNames();
|
|
|
|
|
|
return new AnalysisConfig(bucketSpan, categorizationFieldName, categorizationFilters, categorizationAnalyzerConfig,
|
|
|
- perPartitionCategorizationConfig, latency, summaryCountFieldName, detectors, influencers, multivariateByFields);
|
|
|
+ perPartitionCategorizationConfig, latency, summaryCountFieldName, detectors, influencers, multivariateByFields,
|
|
|
+ modelPruneWindow);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void verifyModelPruneWindow() {
|
|
|
+ if (modelPruneWindow == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ long modelPruneWindowSecs = modelPruneWindow.seconds();
|
|
|
+ long bucketSpanSecs = bucketSpan.seconds();
|
|
|
+
|
|
|
+ if (modelPruneWindowSecs % bucketSpanSecs != 0) {
|
|
|
+ throw ExceptionsHelper.badRequestException(MODEL_PRUNE_WINDOW.getPreferredName() + " [" + modelPruneWindow.toString() + "]"
|
|
|
+ + " must be a multiple of " + BUCKET_SPAN.getPreferredName() + " [" + bucketSpan.toString() + "]");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (modelPruneWindowSecs / bucketSpanSecs < MINIMUM_MODEL_PRUNE_WINDOW_BUCKETS) {
|
|
|
+ throw ExceptionsHelper.badRequestException(MODEL_PRUNE_WINDOW.getPreferredName() + " [" + modelPruneWindow.toString() + "]"
|
|
|
+ + " must be at least " + MINIMUM_MODEL_PRUNE_WINDOW_BUCKETS + " times greater than " + BUCKET_SPAN.getPreferredName()
|
|
|
+ + " [" + bucketSpan.toString() + "]");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private void verifyConfigConsistentWithPerPartitionCategorization() {
|