diff --git a/docs/reference/ml/df-analytics/apis/estimate-memory-usage-dfanalytics.asciidoc b/docs/reference/ml/df-analytics/apis/estimate-memory-usage-dfanalytics.asciidoc
new file mode 100644
index 0000000000000..9f1f77052d647
--- /dev/null
+++ b/docs/reference/ml/df-analytics/apis/estimate-memory-usage-dfanalytics.asciidoc
@@ -0,0 +1,83 @@
+[role="xpack"]
+[testenv="platinum"]
+[[estimate-memory-usage-dfanalytics]]
+=== Estimate memory usage API
+
+[subs="attributes"]
+++++
+Estimate memory usage for {dfanalytics-jobs}
+++++
+
+Estimates memory usage for the given {dataframe-analytics-config}.
+
+experimental[]
+
+[[ml-estimate-memory-usage-dfanalytics-request]]
+==== {api-request-title}
+
+`POST _ml/data_frame/analytics/_estimate_memory_usage`
+
+[[ml-estimate-memory-usage-dfanalytics-prereq]]
+==== {api-prereq-title}
+
+* You must have `monitor_ml` privilege to use this API. For more
+information, see {stack-ov}/security-privileges.html[Security privileges] and
+{stack-ov}/built-in-roles.html[Built-in roles].
+
+[[ml-estimate-memory-usage-dfanalytics-desc]]
+==== {api-description-title}
+
+This API estimates memory usage for the given {dataframe-analytics-config} before the {dfanalytics-job} is even created.
+
+Serves as an advice on how to set `model_memory_limit` when creating {dfanalytics-job}.
+
+[[ml-estimate-memory-usage-dfanalytics-request-body]]
+==== {api-request-body-title}
+
+`data_frame_analytics_config`::
+ (Required, object) Intended configuration of {dfanalytics-job}. For more information, see
+ <>.
+ Note that `id` and `dest` don't need to be provided in the context of this API.
+
+[[ml-estimate-memory-usage-dfanalytics-results]]
+==== {api-response-body-title}
+
+`expected_memory_usage_with_one_partition`::
+ (string) Estimated memory usage under the assumption that the whole {dfanalytics} should happen in memory
+ (i.e. without overflowing to disk).
+
+`expected_memory_usage_with_max_partitions`::
+ (string) Estimated memory usage under the assumption that overflowing to disk is allowed during {dfanalytics}.
+ `expected_memory_usage_with_max_partitions` is usually smaller than `expected_memory_usage_with_one_partition`
+ as using disk allows to limit the main memory needed to perform {dfanalytics}.
+
+[[ml-estimate-memory-usage-dfanalytics-example]]
+==== {api-examples-title}
+
+[source,js]
+--------------------------------------------------
+POST _ml/data_frame/analytics/_estimate_memory_usage
+{
+ "data_frame_analytics_config": {
+ "source": {
+ "index": "logdata"
+ },
+ "analysis": {
+ "outlier_detection": {}
+ }
+ }
+}
+--------------------------------------------------
+// CONSOLE
+// TEST[skip:TBD]
+
+The API returns the following results:
+
+[source,js]
+----
+{
+ "expected_memory_usage_with_one_partition": "128MB",
+ "expected_memory_usage_with_max_partitions": "32MB"
+}
+----
+// TESTRESPONSE
\ No newline at end of file
diff --git a/docs/reference/ml/df-analytics/apis/index.asciidoc b/docs/reference/ml/df-analytics/apis/index.asciidoc
index 416e11f146b70..30e909f3ffad6 100644
--- a/docs/reference/ml/df-analytics/apis/index.asciidoc
+++ b/docs/reference/ml/df-analytics/apis/index.asciidoc
@@ -12,6 +12,7 @@ You can use the following APIs to perform {ml} {dfanalytics} activities.
* <>
* <>
* <>
+* <>
See also <>.
@@ -21,10 +22,11 @@ include::put-dfanalytics.asciidoc[]
include::delete-dfanalytics.asciidoc[]
//EVALUATE
include::evaluate-dfanalytics.asciidoc[]
+//ESTIMATE_MEMORY_USAGE
+include::estimate-memory-usage-dfanalytics.asciidoc[]
//GET
include::get-dfanalytics.asciidoc[]
include::get-dfanalytics-stats.asciidoc[]
//SET/START/STOP
include::start-dfanalytics.asciidoc[]
include::stop-dfanalytics.asciidoc[]
-
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
index 27f4ef093267a..8e2f3414d58b1 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
@@ -90,6 +90,7 @@
import org.elasticsearch.xpack.core.ml.action.DeleteForecastAction;
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
import org.elasticsearch.xpack.core.ml.action.DeleteModelSnapshotAction;
+import org.elasticsearch.xpack.core.ml.action.EstimateMemoryUsageAction;
import org.elasticsearch.xpack.core.ml.action.EvaluateDataFrameAction;
import org.elasticsearch.xpack.core.ml.action.FinalizeJobExecutionAction;
import org.elasticsearch.xpack.core.ml.action.FindFileStructureAction;
@@ -313,6 +314,7 @@ public List> getClientActions() {
DeleteDataFrameAnalyticsAction.INSTANCE,
StartDataFrameAnalyticsAction.INSTANCE,
EvaluateDataFrameAction.INSTANCE,
+ EstimateMemoryUsageAction.INSTANCE,
// security
ClearRealmCacheAction.INSTANCE,
ClearRolesCacheAction.INSTANCE,
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/EstimateMemoryUsageAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/EstimateMemoryUsageAction.java
new file mode 100644
index 0000000000000..62a8220d1a535
--- /dev/null
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/EstimateMemoryUsageAction.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.core.ml.action;
+
+import org.elasticsearch.action.ActionRequest;
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.ActionResponse;
+import org.elasticsearch.action.ActionType;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfig;
+import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
+
+public class EstimateMemoryUsageAction extends ActionType {
+
+ public static final EstimateMemoryUsageAction INSTANCE = new EstimateMemoryUsageAction();
+ public static final String NAME = "cluster:admin/xpack/ml/data_frame/analytics/estimate_memory_usage";
+
+ private EstimateMemoryUsageAction() {
+ super(NAME, EstimateMemoryUsageAction.Response::new);
+ }
+
+ public static class Request extends ActionRequest implements ToXContentObject {
+
+ private static final ParseField DATA_FRAME_ANALYTICS_CONFIG = new ParseField("data_frame_analytics_config");
+
+ private static final ConstructingObjectParser PARSER =
+ new ConstructingObjectParser<>(
+ NAME,
+ args -> {
+ DataFrameAnalyticsConfig.Builder configBuilder = (DataFrameAnalyticsConfig.Builder) args[0];
+ DataFrameAnalyticsConfig config = configBuilder.buildForMemoryEstimation();
+ return new EstimateMemoryUsageAction.Request(config);
+ });
+
+ static {
+ PARSER.declareObject(constructorArg(), DataFrameAnalyticsConfig.STRICT_PARSER, DATA_FRAME_ANALYTICS_CONFIG);
+ }
+
+ public static EstimateMemoryUsageAction.Request parseRequest(XContentParser parser) {
+ return PARSER.apply(parser, null);
+ }
+
+ private final DataFrameAnalyticsConfig config;
+
+ public Request(DataFrameAnalyticsConfig config) {
+ this.config = ExceptionsHelper.requireNonNull(config, DATA_FRAME_ANALYTICS_CONFIG);
+ }
+
+ public Request(StreamInput in) throws IOException {
+ super(in);
+ this.config = new DataFrameAnalyticsConfig(in);
+ }
+
+ @Override
+ public ActionRequestValidationException validate() {
+ return null;
+ }
+
+ public DataFrameAnalyticsConfig getConfig() {
+ return config;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ super.writeTo(out);
+ config.writeTo(out);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(DATA_FRAME_ANALYTICS_CONFIG.getPreferredName(), config);
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ Request that = (Request) other;
+ return Objects.equals(config, that.config);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(config);
+ }
+ }
+
+ public static class Response extends ActionResponse implements ToXContentObject {
+
+ public static final ParseField TYPE = new ParseField("memory_usage_estimation_result");
+
+ public static final ParseField EXPECTED_MEMORY_USAGE_WITH_ONE_PARTITION =
+ new ParseField("expected_memory_usage_with_one_partition");
+ public static final ParseField EXPECTED_MEMORY_USAGE_WITH_MAX_PARTITIONS =
+ new ParseField("expected_memory_usage_with_max_partitions");
+
+ static final ConstructingObjectParser PARSER =
+ new ConstructingObjectParser<>(
+ TYPE.getPreferredName(),
+ args -> new Response((ByteSizeValue) args[0], (ByteSizeValue) args[1]));
+
+ static {
+ PARSER.declareField(
+ optionalConstructorArg(),
+ (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), EXPECTED_MEMORY_USAGE_WITH_ONE_PARTITION.getPreferredName()),
+ EXPECTED_MEMORY_USAGE_WITH_ONE_PARTITION,
+ ObjectParser.ValueType.VALUE);
+ PARSER.declareField(
+ optionalConstructorArg(),
+ (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), EXPECTED_MEMORY_USAGE_WITH_MAX_PARTITIONS.getPreferredName()),
+ EXPECTED_MEMORY_USAGE_WITH_MAX_PARTITIONS,
+ ObjectParser.ValueType.VALUE);
+ }
+
+ private final ByteSizeValue expectedMemoryUsageWithOnePartition;
+ private final ByteSizeValue expectedMemoryUsageWithMaxPartitions;
+
+ public Response(@Nullable ByteSizeValue expectedMemoryUsageWithOnePartition,
+ @Nullable ByteSizeValue expectedMemoryUsageWithMaxPartitions) {
+ this.expectedMemoryUsageWithOnePartition = expectedMemoryUsageWithOnePartition;
+ this.expectedMemoryUsageWithMaxPartitions = expectedMemoryUsageWithMaxPartitions;
+ }
+
+ public Response(StreamInput in) throws IOException {
+ super(in);
+ this.expectedMemoryUsageWithOnePartition = in.readOptionalWriteable(ByteSizeValue::new);
+ this.expectedMemoryUsageWithMaxPartitions = in.readOptionalWriteable(ByteSizeValue::new);
+ }
+
+ public ByteSizeValue getExpectedMemoryUsageWithOnePartition() {
+ return expectedMemoryUsageWithOnePartition;
+ }
+
+ public ByteSizeValue getExpectedMemoryUsageWithMaxPartitions() {
+ return expectedMemoryUsageWithMaxPartitions;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ out.writeOptionalWriteable(expectedMemoryUsageWithOnePartition);
+ out.writeOptionalWriteable(expectedMemoryUsageWithMaxPartitions);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ if (expectedMemoryUsageWithOnePartition != null) {
+ builder.field(
+ EXPECTED_MEMORY_USAGE_WITH_ONE_PARTITION.getPreferredName(), expectedMemoryUsageWithOnePartition.getStringRep());
+ }
+ if (expectedMemoryUsageWithMaxPartitions != null) {
+ builder.field(
+ EXPECTED_MEMORY_USAGE_WITH_MAX_PARTITIONS.getPreferredName(), expectedMemoryUsageWithMaxPartitions.getStringRep());
+ }
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ Response that = (Response) other;
+ return Objects.equals(expectedMemoryUsageWithOnePartition, that.expectedMemoryUsageWithOnePartition)
+ && Objects.equals(expectedMemoryUsageWithMaxPartitions, that.expectedMemoryUsageWithMaxPartitions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(expectedMemoryUsageWithOnePartition, expectedMemoryUsageWithMaxPartitions);
+ }
+ }
+}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/dataframe/DataFrameAnalyticsConfig.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/dataframe/DataFrameAnalyticsConfig.java
index efb76a8963850..f194d108ad0b8 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/dataframe/DataFrameAnalyticsConfig.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/dataframe/DataFrameAnalyticsConfig.java
@@ -57,7 +57,7 @@ public class DataFrameAnalyticsConfig implements ToXContentObject, Writeable {
public static final ObjectParser STRICT_PARSER = createParser(false);
public static final ObjectParser LENIENT_PARSER = createParser(true);
- public static ObjectParser createParser(boolean ignoreUnknownFields) {
+ private static ObjectParser createParser(boolean ignoreUnknownFields) {
ObjectParser parser = new ObjectParser<>(TYPE, ignoreUnknownFields, Builder::new);
parser.declareString((c, s) -> {}, CONFIG_TYPE);
@@ -281,14 +281,6 @@ public static class Builder {
public Builder() {}
- public Builder(String id) {
- setId(id);
- }
-
- public Builder(ByteSizeValue maxModelMemoryLimit) {
- this.maxModelMemoryLimit = maxModelMemoryLimit;
- }
-
public Builder(DataFrameAnalyticsConfig config) {
this(config, null);
}
@@ -343,30 +335,10 @@ public Builder setHeaders(Map headers) {
}
public Builder setModelMemoryLimit(ByteSizeValue modelMemoryLimit) {
- if (modelMemoryLimit != null && modelMemoryLimit.compareTo(MIN_MODEL_MEMORY_LIMIT) < 0) {
- throw new IllegalArgumentException("[" + MODEL_MEMORY_LIMIT.getPreferredName()
- + "] must be at least [" + MIN_MODEL_MEMORY_LIMIT.getStringRep() + "]");
- }
this.modelMemoryLimit = modelMemoryLimit;
return this;
}
- private void applyMaxModelMemoryLimit() {
-
- boolean maxModelMemoryIsSet = maxModelMemoryLimit != null && maxModelMemoryLimit.getMb() > 0;
-
- if (modelMemoryLimit == null) {
- // Default is silently capped if higher than limit
- if (maxModelMemoryIsSet && DEFAULT_MODEL_MEMORY_LIMIT.compareTo(maxModelMemoryLimit) > 0) {
- modelMemoryLimit = maxModelMemoryLimit;
- }
- } else if (maxModelMemoryIsSet && modelMemoryLimit.compareTo(maxModelMemoryLimit) > 0) {
- // Explicit setting higher than limit is an error
- throw ExceptionsHelper.badRequestException(Messages.getMessage(Messages.JOB_CONFIG_MODEL_MEMORY_LIMIT_GREATER_THAN_MAX,
- modelMemoryLimit, maxModelMemoryLimit));
- }
- }
-
public Builder setCreateTime(Instant createTime) {
this.createTime = createTime;
return this;
@@ -377,9 +349,53 @@ public Builder setVersion(Version version) {
return this;
}
+ /**
+ * Builds {@link DataFrameAnalyticsConfig} object.
+ */
public DataFrameAnalyticsConfig build() {
applyMaxModelMemoryLimit();
return new DataFrameAnalyticsConfig(id, source, dest, analysis, headers, modelMemoryLimit, analyzedFields, createTime, version);
}
+
+ /**
+ * Builds {@link DataFrameAnalyticsConfig} object for the purpose of performing memory estimation.
+ * Some fields (i.e. "id", "dest") may not be present, therefore we overwrite them here to make {@link DataFrameAnalyticsConfig}'s
+ * constructor validations happy.
+ */
+ public DataFrameAnalyticsConfig buildForMemoryEstimation() {
+ return new DataFrameAnalyticsConfig(
+ id != null ? id : "dummy",
+ source,
+ dest != null ? dest : new DataFrameAnalyticsDest("dummy", null),
+ analysis,
+ headers,
+ modelMemoryLimit,
+ analyzedFields,
+ createTime,
+ version);
+ }
+
+ private void applyMaxModelMemoryLimit() {
+ boolean maxModelMemoryIsSet = maxModelMemoryLimit != null && maxModelMemoryLimit.getMb() > 0;
+
+ if (modelMemoryLimit != null) {
+ if (modelMemoryLimit.compareTo(MIN_MODEL_MEMORY_LIMIT) < 0) {
+ // Explicit setting lower than minimum is an error
+ throw ExceptionsHelper.badRequestException(
+ Messages.getMessage(Messages.JOB_CONFIG_MODEL_MEMORY_LIMIT_TOO_LOW, modelMemoryLimit));
+ }
+ if (maxModelMemoryIsSet && modelMemoryLimit.compareTo(maxModelMemoryLimit) > 0) {
+ // Explicit setting higher than limit is an error
+ throw ExceptionsHelper.badRequestException(
+ Messages.getMessage(
+ Messages.JOB_CONFIG_MODEL_MEMORY_LIMIT_GREATER_THAN_MAX, modelMemoryLimit, maxModelMemoryLimit));
+ }
+ } else {
+ // Default is silently capped if higher than limit
+ if (maxModelMemoryIsSet && DEFAULT_MODEL_MEMORY_LIMIT.compareTo(maxModelMemoryLimit) > 0) {
+ modelMemoryLimit = maxModelMemoryLimit;
+ }
+ }
+ }
}
}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/messages/Messages.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/messages/Messages.java
index dfb95d2adac33..f5e66fed8a882 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/messages/Messages.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/messages/Messages.java
@@ -122,7 +122,7 @@ public final class Messages {
"Invalid detector rule: scope field ''{0}'' is invalid; select from {1}";
public static final String JOB_CONFIG_FIELDNAME_INCOMPATIBLE_FUNCTION = "field_name cannot be used with function ''{0}''";
public static final String JOB_CONFIG_FIELD_VALUE_TOO_LOW = "{0} cannot be less than {1,number}. Value = {2,number}";
- public static final String JOB_CONFIG_MODEL_MEMORY_LIMIT_TOO_LOW = "model_memory_limit must be at least 1 MiB. Value = {0,number}";
+ public static final String JOB_CONFIG_MODEL_MEMORY_LIMIT_TOO_LOW = "model_memory_limit must be at least 1 MiB. Value = {0}";
public static final String JOB_CONFIG_MODEL_MEMORY_LIMIT_GREATER_THAN_MAX =
"model_memory_limit [{0}] must be less than the value of the " +
MachineLearningField.MAX_MODEL_MEMORY_LIMIT.getKey() +
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/EstimateMemoryUsageActionRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/EstimateMemoryUsageActionRequestTests.java
new file mode 100644
index 0000000000000..6a8f82412e99a
--- /dev/null
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/EstimateMemoryUsageActionRequestTests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.core.ml.action;
+
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.NamedXContentRegistry;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.SearchModule;
+import org.elasticsearch.test.AbstractSerializingTestCase;
+import org.elasticsearch.xpack.core.ml.action.EstimateMemoryUsageAction.Request;
+import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfigTests;
+import org.elasticsearch.xpack.core.ml.dataframe.analyses.MlDataFrameAnalysisNamedXContentProvider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class EstimateMemoryUsageActionRequestTests extends AbstractSerializingTestCase {
+
+ @Override
+ protected NamedWriteableRegistry getNamedWriteableRegistry() {
+ List namedWriteables = new ArrayList<>();
+ namedWriteables.addAll(new MlDataFrameAnalysisNamedXContentProvider().getNamedWriteables());
+ namedWriteables.addAll(new SearchModule(Settings.EMPTY, Collections.emptyList()).getNamedWriteables());
+ return new NamedWriteableRegistry(namedWriteables);
+ }
+
+ @Override
+ protected NamedXContentRegistry xContentRegistry() {
+ List namedXContent = new ArrayList<>();
+ namedXContent.addAll(new MlDataFrameAnalysisNamedXContentProvider().getNamedXContentParsers());
+ namedXContent.addAll(new SearchModule(Settings.EMPTY, Collections.emptyList()).getNamedXContents());
+ return new NamedXContentRegistry(namedXContent);
+ }
+
+ @Override
+ protected Request createTestInstance() {
+ return new Request(DataFrameAnalyticsConfigTests.createRandom("dummy"));
+ }
+
+ @Override
+ protected Writeable.Reader instanceReader() {
+ return Request::new;
+ }
+
+ @Override
+ protected Request doParseInstance(XContentParser parser) {
+ return Request.parseRequest(parser);
+ }
+}
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/EstimateMemoryUsageActionResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/EstimateMemoryUsageActionResponseTests.java
new file mode 100644
index 0000000000000..e6b9f4a99a25d
--- /dev/null
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/EstimateMemoryUsageActionResponseTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.core.ml.action;
+
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.AbstractSerializingTestCase;
+import org.elasticsearch.xpack.core.ml.action.EstimateMemoryUsageAction.Response;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+
+public class EstimateMemoryUsageActionResponseTests extends AbstractSerializingTestCase {
+
+ @Override
+ protected Response createTestInstance() {
+ return new Response(
+ randomBoolean() ? new ByteSizeValue(randomNonNegativeLong()) : null,
+ randomBoolean() ? new ByteSizeValue(randomNonNegativeLong()) : null);
+ }
+
+ @Override
+ protected Writeable.Reader instanceReader() {
+ return Response::new;
+ }
+
+ @Override
+ protected Response doParseInstance(XContentParser parser) {
+ return Response.PARSER.apply(parser, null);
+ }
+
+ public void testConstructor_NullValues() {
+ Response response = new Response(null, null);
+ assertThat(response.getExpectedMemoryUsageWithOnePartition(), nullValue());
+ assertThat(response.getExpectedMemoryUsageWithMaxPartitions(), nullValue());
+ }
+
+ public void testConstructor() {
+ Response response = new Response(new ByteSizeValue(2048), new ByteSizeValue(1024));
+ assertThat(response.getExpectedMemoryUsageWithOnePartition(), equalTo(new ByteSizeValue(2048)));
+ assertThat(response.getExpectedMemoryUsageWithMaxPartitions(), equalTo(new ByteSizeValue(1024)));
+ }
+}
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/dataframe/DataFrameAnalyticsConfigTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/dataframe/DataFrameAnalyticsConfigTests.java
index 5464181d17b1d..f718576611524 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/dataframe/DataFrameAnalyticsConfigTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/dataframe/DataFrameAnalyticsConfigTests.java
@@ -43,7 +43,6 @@
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasSize;
@@ -227,18 +226,18 @@ public void testInvalidModelMemoryLimits() {
DataFrameAnalyticsConfig.Builder builder = new DataFrameAnalyticsConfig.Builder();
// All these are different ways of specifying a limit that is lower than the minimum
- assertTooSmall(expectThrows(IllegalArgumentException.class,
- () -> builder.setModelMemoryLimit(new ByteSizeValue(1048575, ByteSizeUnit.BYTES))));
- assertTooSmall(expectThrows(IllegalArgumentException.class,
- () -> builder.setModelMemoryLimit(new ByteSizeValue(0, ByteSizeUnit.BYTES))));
- assertTooSmall(expectThrows(IllegalArgumentException.class,
- () -> builder.setModelMemoryLimit(new ByteSizeValue(-1, ByteSizeUnit.BYTES))));
- assertTooSmall(expectThrows(IllegalArgumentException.class,
- () -> builder.setModelMemoryLimit(new ByteSizeValue(1023, ByteSizeUnit.KB))));
- assertTooSmall(expectThrows(IllegalArgumentException.class,
- () -> builder.setModelMemoryLimit(new ByteSizeValue(0, ByteSizeUnit.KB))));
- assertTooSmall(expectThrows(IllegalArgumentException.class,
- () -> builder.setModelMemoryLimit(new ByteSizeValue(0, ByteSizeUnit.MB))));
+ assertTooSmall(expectThrows(ElasticsearchStatusException.class,
+ () -> builder.setModelMemoryLimit(new ByteSizeValue(1048575, ByteSizeUnit.BYTES)).build()));
+ assertTooSmall(expectThrows(ElasticsearchStatusException.class,
+ () -> builder.setModelMemoryLimit(new ByteSizeValue(0, ByteSizeUnit.BYTES)).build()));
+ assertTooSmall(expectThrows(ElasticsearchStatusException.class,
+ () -> builder.setModelMemoryLimit(new ByteSizeValue(-1, ByteSizeUnit.BYTES)).build()));
+ assertTooSmall(expectThrows(ElasticsearchStatusException.class,
+ () -> builder.setModelMemoryLimit(new ByteSizeValue(1023, ByteSizeUnit.KB)).build()));
+ assertTooSmall(expectThrows(ElasticsearchStatusException.class,
+ () -> builder.setModelMemoryLimit(new ByteSizeValue(0, ByteSizeUnit.KB)).build()));
+ assertTooSmall(expectThrows(ElasticsearchStatusException.class,
+ () -> builder.setModelMemoryLimit(new ByteSizeValue(0, ByteSizeUnit.MB)).build()));
}
public void testNoMemoryCapping() {
@@ -276,6 +275,36 @@ public void testExplicitModelMemoryLimitTooHigh() {
assertThat(e.getMessage(), containsString("must be less than the value of the xpack.ml.max_model_memory_limit setting"));
}
+ public void testBuildForMemoryEstimation() {
+ DataFrameAnalyticsConfig.Builder builder = createRandomBuilder("foo");
+
+ DataFrameAnalyticsConfig config = builder.buildForMemoryEstimation();
+
+ assertThat(config, equalTo(builder.build()));
+ }
+
+ public void testBuildForMemoryEstimation_MissingId() {
+ DataFrameAnalyticsConfig.Builder builder = new DataFrameAnalyticsConfig.Builder()
+ .setAnalysis(OutlierDetectionTests.createRandom())
+ .setSource(DataFrameAnalyticsSourceTests.createRandom())
+ .setDest(DataFrameAnalyticsDestTests.createRandom());
+
+ DataFrameAnalyticsConfig config = builder.buildForMemoryEstimation();
+
+ assertThat(config.getId(), equalTo("dummy"));
+ }
+
+ public void testBuildForMemoryEstimation_MissingDest() {
+ DataFrameAnalyticsConfig.Builder builder = new DataFrameAnalyticsConfig.Builder()
+ .setId("foo")
+ .setAnalysis(OutlierDetectionTests.createRandom())
+ .setSource(DataFrameAnalyticsSourceTests.createRandom());
+
+ DataFrameAnalyticsConfig config = builder.buildForMemoryEstimation();
+
+ assertThat(config.getDest().getIndex(), equalTo("dummy"));
+ }
+
public void testPreventCreateTimeInjection() throws IOException {
String json = "{"
+ " \"create_time\" : 123456789 },"
@@ -306,7 +335,7 @@ public void testPreventVersionInjection() throws IOException {
}
}
- public void assertTooSmall(IllegalArgumentException e) {
- assertThat(e.getMessage(), is("[model_memory_limit] must be at least [1mb]"));
+ private static void assertTooSmall(ElasticsearchStatusException e) {
+ assertThat(e.getMessage(), startsWith("model_memory_limit must be at least 1 MiB."));
}
}
diff --git a/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/MlNativeDataFrameAnalyticsIntegTestCase.java b/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/MlNativeDataFrameAnalyticsIntegTestCase.java
index 520f7a30ece4f..24045c1549151 100644
--- a/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/MlNativeDataFrameAnalyticsIntegTestCase.java
+++ b/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/MlNativeDataFrameAnalyticsIntegTestCase.java
@@ -105,8 +105,9 @@ protected static String createJsonRecord(Map keyValueMap) throws
}
protected static DataFrameAnalyticsConfig buildOutlierDetectionAnalytics(String id, String[] sourceIndex, String destIndex,
- @Nullable String resultsField) {
- DataFrameAnalyticsConfig.Builder configBuilder = new DataFrameAnalyticsConfig.Builder(id);
+ @Nullable String resultsField) {
+ DataFrameAnalyticsConfig.Builder configBuilder = new DataFrameAnalyticsConfig.Builder();
+ configBuilder.setId(id);
configBuilder.setSource(new DataFrameAnalyticsSource(sourceIndex, null));
configBuilder.setDest(new DataFrameAnalyticsDest(destIndex, resultsField));
configBuilder.setAnalysis(new OutlierDetection());
@@ -122,7 +123,8 @@ protected void assertState(String id, DataFrameAnalyticsState state) {
protected static DataFrameAnalyticsConfig buildRegressionAnalytics(String id, String[] sourceIndex, String destIndex,
@Nullable String resultsField, String dependentVariable) {
- DataFrameAnalyticsConfig.Builder configBuilder = new DataFrameAnalyticsConfig.Builder(id);
+ DataFrameAnalyticsConfig.Builder configBuilder = new DataFrameAnalyticsConfig.Builder();
+ configBuilder.setId(id);
configBuilder.setSource(new DataFrameAnalyticsSource(sourceIndex, null));
configBuilder.setDest(new DataFrameAnalyticsDest(destIndex, resultsField));
configBuilder.setAnalysis(new Regression(dependentVariable));
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java
index 3eb554ff45407..954dbbcf65fcc 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java
@@ -73,6 +73,7 @@
import org.elasticsearch.xpack.core.ml.action.DeleteForecastAction;
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
import org.elasticsearch.xpack.core.ml.action.DeleteModelSnapshotAction;
+import org.elasticsearch.xpack.core.ml.action.EstimateMemoryUsageAction;
import org.elasticsearch.xpack.core.ml.action.EvaluateDataFrameAction;
import org.elasticsearch.xpack.core.ml.action.FinalizeJobExecutionAction;
import org.elasticsearch.xpack.core.ml.action.FindFileStructureAction;
@@ -137,6 +138,7 @@
import org.elasticsearch.xpack.ml.action.TransportDeleteForecastAction;
import org.elasticsearch.xpack.ml.action.TransportDeleteJobAction;
import org.elasticsearch.xpack.ml.action.TransportDeleteModelSnapshotAction;
+import org.elasticsearch.xpack.ml.action.TransportEstimateMemoryUsageAction;
import org.elasticsearch.xpack.ml.action.TransportEvaluateDataFrameAction;
import org.elasticsearch.xpack.ml.action.TransportFinalizeJobExecutionAction;
import org.elasticsearch.xpack.ml.action.TransportFindFileStructureAction;
@@ -191,6 +193,10 @@
import org.elasticsearch.xpack.ml.dataframe.persistence.DataFrameAnalyticsConfigProvider;
import org.elasticsearch.xpack.ml.dataframe.process.AnalyticsProcessFactory;
import org.elasticsearch.xpack.ml.dataframe.process.AnalyticsProcessManager;
+import org.elasticsearch.xpack.ml.dataframe.process.results.AnalyticsResult;
+import org.elasticsearch.xpack.ml.dataframe.process.MemoryUsageEstimationProcessManager;
+import org.elasticsearch.xpack.ml.dataframe.process.results.MemoryUsageEstimationResult;
+import org.elasticsearch.xpack.ml.dataframe.process.NativeMemoryUsageEstimationProcessFactory;
import org.elasticsearch.xpack.ml.dataframe.process.NativeAnalyticsProcessFactory;
import org.elasticsearch.xpack.ml.job.JobManager;
import org.elasticsearch.xpack.ml.job.JobManagerHolder;
@@ -238,6 +244,7 @@
import org.elasticsearch.xpack.ml.rest.datafeeds.RestStopDatafeedAction;
import org.elasticsearch.xpack.ml.rest.datafeeds.RestUpdateDatafeedAction;
import org.elasticsearch.xpack.ml.rest.dataframe.RestDeleteDataFrameAnalyticsAction;
+import org.elasticsearch.xpack.ml.rest.dataframe.RestEstimateMemoryUsageAction;
import org.elasticsearch.xpack.ml.rest.dataframe.RestEvaluateDataFrameAction;
import org.elasticsearch.xpack.ml.rest.dataframe.RestGetDataFrameAnalyticsAction;
import org.elasticsearch.xpack.ml.rest.dataframe.RestGetDataFrameAnalyticsStatsAction;
@@ -489,7 +496,8 @@ public Collection