Add a "verbose" option to the data frame analytics stats endpoint (#59589) (#59621)

This commit is contained in:
Przemysław Witek 2020-07-16 09:51:31 +02:00 committed by GitHub
parent 6db481f49e
commit df4fea79cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 213 additions and 20 deletions

View File

@ -57,6 +57,10 @@ include::{es-repo-dir}/ml/ml-shared.asciidoc[tag=from]
(Optional, integer)
include::{es-repo-dir}/ml/ml-shared.asciidoc[tag=size]
`verbose`::
(Optional, boolean)
include::{es-repo-dir}/ml/ml-shared.asciidoc[tag=verbose]
[role="child_attributes"]
[[ml-get-dfanalytics-stats-response-body]]
==== {api-response-body-title}

View File

@ -1384,3 +1384,7 @@ tag::use-null[]
Defines whether a new series is used as the null series when there is no value
for the by or partition fields. The default value is `false`.
end::use-null[]
tag::verbose[]
Defines whether the stats response should be verbose. The default value is `false`.
end::verbose[]

View File

@ -761,6 +761,16 @@ public class Strings {
return toString(toXContent, false, false);
}
/**
* Return a {@link String} that is the json representation of the provided {@link ToXContent}.
* Wraps the output into an anonymous object if needed.
* Allows to configure the params.
* The content is not pretty-printed nor human readable.
*/
public static String toString(ToXContent toXContent, ToXContent.Params params) {
return toString(toXContent, params, false, false);
}
/**
* Returns a string representation of the builder (only applicable for text based xcontent).
* @param xContentBuilder builder containing an object to converted to a string
@ -776,12 +786,22 @@ public class Strings {
*
*/
public static String toString(ToXContent toXContent, boolean pretty, boolean human) {
return toString(toXContent, ToXContent.EMPTY_PARAMS, pretty, human);
}
/**
* Return a {@link String} that is the json representation of the provided {@link ToXContent}.
* Wraps the output into an anonymous object if needed.
* Allows to configure the params.
* Allows to control whether the outputted json needs to be pretty printed and human readable.
*/
private static String toString(ToXContent toXContent, ToXContent.Params params, boolean pretty, boolean human) {
try {
XContentBuilder builder = createBuilder(pretty, human);
if (toXContent.isFragment()) {
builder.startObject();
}
toXContent.toXContent(builder, ToXContent.EMPTY_PARAMS);
toXContent.toXContent(builder, params);
if (toXContent.isFragment()) {
builder.endObject();
}

View File

@ -24,6 +24,8 @@ import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.test.ESTestCase;
import java.util.Collections;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
@ -98,6 +100,16 @@ public class StringsTests extends ESTestCase {
}
}
public void testToStringToXContentWithOrWithoutParams() {
ToXContent toXContent = (builder, params) -> builder.field("color_from_param", params.param("color", "red"));
// Rely on the default value of "color" param when params are not passed
assertThat(Strings.toString(toXContent), containsString("\"color_from_param\":\"red\""));
// Pass "color" param explicitly
assertThat(
Strings.toString(toXContent, new ToXContent.MapParams(Collections.singletonMap("color", "blue"))),
containsString("\"color_from_param\":\"blue\""));
}
public void testSplitStringToSet() {
assertEquals(Strings.tokenizeByCommaToSet(null), Sets.newHashSet());
assertEquals(Strings.tokenizeByCommaToSet(""), Sets.newHashSet());

View File

@ -33,6 +33,7 @@ import org.elasticsearch.xpack.core.ml.dataframe.stats.common.MemoryUsage;
import org.elasticsearch.xpack.core.ml.dataframe.stats.common.DataCounts;
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
import org.elasticsearch.xpack.core.ml.utils.PhaseProgress;
import org.elasticsearch.xpack.core.ml.utils.ToXContentParams;
import java.io.IOException;
import java.util.Arrays;
@ -141,7 +142,9 @@ public class GetDataFrameAnalyticsStatsAction extends ActionType<GetDataFrameAna
return false;
}
Request other = (Request) obj;
return Objects.equals(id, other.id) && allowNoMatch == other.allowNoMatch && Objects.equals(pageParams, other.pageParams);
return Objects.equals(id, other.id)
&& allowNoMatch == other.allowNoMatch
&& Objects.equals(pageParams, other.pageParams);
}
}
@ -154,6 +157,9 @@ public class GetDataFrameAnalyticsStatsAction extends ActionType<GetDataFrameAna
public static class Response extends BaseTasksResponse implements ToXContentObject {
/** Name of the response's REST param which is used to determine whether this response should be verbose. */
public static final String VERBOSE = "verbose";
public static class Stats implements ToXContentObject, Writeable {
private final String id;
@ -295,12 +301,12 @@ public class GetDataFrameAnalyticsStatsAction extends ActionType<GetDataFrameAna
// TODO: Have callers wrap the content with an object as they choose rather than forcing it upon them
builder.startObject();
{
toUnwrappedXContent(builder);
toUnwrappedXContent(builder, params);
}
return builder.endObject();
}
public XContentBuilder toUnwrappedXContent(XContentBuilder builder) throws IOException {
private XContentBuilder toUnwrappedXContent(XContentBuilder builder, Params params) throws IOException {
builder.field(DataFrameAnalyticsConfig.ID.getPreferredName(), id);
builder.field("state", state.toString());
if (failureReason != null) {
@ -313,7 +319,12 @@ public class GetDataFrameAnalyticsStatsAction extends ActionType<GetDataFrameAna
builder.field("memory_usage", memoryUsage);
if (analysisStats != null) {
builder.startObject("analysis_stats");
builder.field(analysisStats.getWriteableName(), analysisStats);
builder.field(
analysisStats.getWriteableName(),
analysisStats,
new MapParams(
Collections.singletonMap(
ToXContentParams.FOR_INTERNAL_STORAGE, Boolean.toString(params.paramAsBoolean(VERBOSE, false)))));
builder.endObject();
}
if (node != null) {

View File

@ -115,7 +115,7 @@ public class ClassificationStats implements AnalysisStats {
builder.field(ITERATION.getPreferredName(), iteration);
builder.field(HYPERPARAMETERS.getPreferredName(), hyperparameters);
builder.field(TIMING_STATS.getPreferredName(), timingStats);
builder.field(VALIDATION_LOSS.getPreferredName(), validationLoss);
builder.field(VALIDATION_LOSS.getPreferredName(), validationLoss, params);
builder.endObject();
return builder;
}

View File

@ -14,6 +14,7 @@ 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.stats.common.FoldValues;
import org.elasticsearch.xpack.core.ml.utils.ToXContentParams;
import java.io.IOException;
import java.util.List;
@ -62,7 +63,9 @@ public class ValidationLoss implements ToXContentObject, Writeable {
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(LOSS_TYPE.getPreferredName(), lossType);
if (params.paramAsBoolean(ToXContentParams.FOR_INTERNAL_STORAGE, false)) {
builder.field(FOLD_VALUES.getPreferredName(), foldValues);
}
builder.endObject();
return builder;
}

View File

@ -115,7 +115,7 @@ public class RegressionStats implements AnalysisStats {
builder.field(ITERATION.getPreferredName(), iteration);
builder.field(HYPERPARAMETERS.getPreferredName(), hyperparameters);
builder.field(TIMING_STATS.getPreferredName(), timingStats);
builder.field(VALIDATION_LOSS.getPreferredName(), validationLoss);
builder.field(VALIDATION_LOSS.getPreferredName(), validationLoss, params);
builder.endObject();
return builder;
}

View File

@ -14,6 +14,7 @@ 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.stats.common.FoldValues;
import org.elasticsearch.xpack.core.ml.utils.ToXContentParams;
import java.io.IOException;
import java.util.List;
@ -62,7 +63,9 @@ public class ValidationLoss implements ToXContentObject, Writeable {
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(LOSS_TYPE.getPreferredName(), lossType);
if (params.paramAsBoolean(ToXContentParams.FOR_INTERNAL_STORAGE, false)) {
builder.field(FOLD_VALUES.getPreferredName(), foldValues);
}
builder.endObject();
return builder;
}

View File

@ -9,6 +9,7 @@ package org.elasticsearch.xpack.core.ml.action;
import org.elasticsearch.test.ESTestCase;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
public class GetDataFrameAnalyticsStatsActionRequestTests extends ESTestCase {
@ -31,4 +32,11 @@ public class GetDataFrameAnalyticsStatsActionRequestTests extends ESTestCase {
assertThat(request.getId(), equalTo("foo"));
}
public void testSetAllowNoMatch() {
GetDataFrameAnalyticsStatsAction.Request request = new GetDataFrameAnalyticsStatsAction.Request();
assertThat(request.isAllowNoMatch(), is(true));
request.setAllowNoMatch(false);
assertThat(request.isAllowNoMatch(), is(false));
}
}

View File

@ -5,8 +5,10 @@
*/
package org.elasticsearch.xpack.core.ml.action;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.core.action.util.QueryPage;
import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsStatsAction.Response;
@ -14,6 +16,7 @@ import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfigTests;
import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsState;
import org.elasticsearch.xpack.core.ml.dataframe.stats.AnalysisStats;
import org.elasticsearch.xpack.core.ml.dataframe.stats.AnalysisStatsNamedWriteablesProvider;
import org.elasticsearch.xpack.core.ml.dataframe.stats.classification.ValidationLoss;
import org.elasticsearch.xpack.core.ml.dataframe.stats.common.MemoryUsage;
import org.elasticsearch.xpack.core.ml.dataframe.stats.common.MemoryUsageTests;
import org.elasticsearch.xpack.core.ml.dataframe.stats.classification.ClassificationStatsTests;
@ -26,9 +29,12 @@ import org.elasticsearch.xpack.core.ml.utils.PhaseProgress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
public class GetDataFrameAnalyticsStatsActionResponseTests extends AbstractWireSerializingTestCase<Response> {
@ -40,6 +46,17 @@ public class GetDataFrameAnalyticsStatsActionResponseTests extends AbstractWireS
}
public static Response randomResponse(int listSize) {
return randomResponse(
listSize,
() -> randomBoolean()
? null
: randomFrom(
ClassificationStatsTests.createRandom(),
OutlierDetectionStatsTests.createRandom(),
RegressionStatsTests.createRandom()));
}
private static Response randomResponse(int listSize, Supplier<AnalysisStats> analysisStatsSupplier) {
List<Response.Stats> analytics = new ArrayList<>(listSize);
for (int j = 0; j < listSize; j++) {
String failureReason = randomBoolean() ? null : randomAlphaOfLength(10);
@ -49,12 +66,7 @@ public class GetDataFrameAnalyticsStatsActionResponseTests extends AbstractWireS
new PhaseProgress(randomAlphaOfLength(10), randomIntBetween(0, 100))));
DataCounts dataCounts = randomBoolean() ? null : DataCountsTests.createRandom();
MemoryUsage memoryUsage = randomBoolean() ? null : MemoryUsageTests.createRandom();
AnalysisStats analysisStats = randomBoolean() ? null :
randomFrom(
ClassificationStatsTests.createRandom(),
OutlierDetectionStatsTests.createRandom(),
RegressionStatsTests.createRandom()
);
AnalysisStats analysisStats = analysisStatsSupplier.get();
Response.Stats stats = new Response.Stats(DataFrameAnalyticsConfigTests.randomValidId(),
randomFrom(DataFrameAnalyticsState.values()), failureReason, progress, dataCounts, memoryUsage, analysisStats, null,
randomAlphaOfLength(20));
@ -88,4 +100,24 @@ public class GetDataFrameAnalyticsStatsActionResponseTests extends AbstractWireS
assertThat(stats.getDataCounts(), equalTo(new DataCounts(stats.getId())));
assertThat(stats.getMemoryUsage(), equalTo(new MemoryUsage(stats.getId())));
}
public void testVerbose() {
String foldValuesFieldName = ValidationLoss.FOLD_VALUES.getPreferredName();
// Create response for supervised analysis that is certain to contain fold_values field
Response response =
randomResponse(1, () -> randomFrom(ClassificationStatsTests.createRandom(), RegressionStatsTests.createRandom()));
// VERBOSE param defaults to "false", fold values *not* outputted
assertThat(Strings.toString(response), not(containsString(foldValuesFieldName)));
// VERBOSE param explicitly set to "false", fold values *not* outputted
assertThat(
Strings.toString(response, new ToXContent.MapParams(Collections.singletonMap(Response.VERBOSE, "false"))),
not(containsString(foldValuesFieldName)));
// VERBOSE param explicitly set to "true", fold values outputted
assertThat(
Strings.toString(response, new ToXContent.MapParams(Collections.singletonMap(Response.VERBOSE, "true"))),
containsString(foldValuesFieldName));
}
}

View File

@ -6,13 +6,20 @@
package org.elasticsearch.xpack.core.ml.dataframe.stats.classification;
import org.elasticsearch.Version;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.xpack.core.ml.AbstractBWCSerializationTestCase;
import org.elasticsearch.xpack.core.ml.dataframe.stats.common.FoldValuesTests;
import org.elasticsearch.xpack.core.ml.utils.ToXContentParams;
import org.junit.Before;
import java.io.IOException;
import java.util.Collections;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
public class ValidationLossTests extends AbstractBWCSerializationTestCase<ValidationLoss> {
@ -28,6 +35,11 @@ public class ValidationLossTests extends AbstractBWCSerializationTestCase<Valida
return ValidationLoss.fromXContent(parser, lenient);
}
@Override
protected ToXContent.Params getToXContentParams() {
return new ToXContent.MapParams(Collections.singletonMap(ToXContentParams.FOR_INTERNAL_STORAGE, "true"));
}
@Override
protected Writeable.Reader<ValidationLoss> instanceReader() {
return ValidationLoss::new;
@ -41,7 +53,7 @@ public class ValidationLossTests extends AbstractBWCSerializationTestCase<Valida
public static ValidationLoss createRandom() {
return new ValidationLoss(
randomAlphaOfLength(10),
randomList(5, () -> FoldValuesTests.createRandom())
randomList(5, FoldValuesTests::createRandom)
);
}
@ -49,4 +61,26 @@ public class ValidationLossTests extends AbstractBWCSerializationTestCase<Valida
protected ValidationLoss mutateInstanceForVersion(ValidationLoss instance, Version version) {
return instance;
}
public void testValidationLossForStats() {
String foldValuesFieldName = ValidationLoss.FOLD_VALUES.getPreferredName();
ValidationLoss validationLoss = createTestInstance();
// FOR_INTERNAL_STORAGE param defaults to "false", fold values *not* outputted
assertThat(Strings.toString(validationLoss), not(containsString(foldValuesFieldName)));
// FOR_INTERNAL_STORAGE param explicitly set to "false", fold values *not* outputted
assertThat(
Strings.toString(
validationLoss,
new ToXContent.MapParams(Collections.singletonMap(ToXContentParams.FOR_INTERNAL_STORAGE, "false"))),
not(containsString(foldValuesFieldName)));
// FOR_INTERNAL_STORAGE param explicitly set to "true", fold values are outputted
assertThat(
Strings.toString(
validationLoss,
new ToXContent.MapParams(Collections.singletonMap(ToXContentParams.FOR_INTERNAL_STORAGE, "true"))),
containsString(foldValuesFieldName));
}
}

View File

@ -6,13 +6,20 @@
package org.elasticsearch.xpack.core.ml.dataframe.stats.regression;
import org.elasticsearch.Version;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.xpack.core.ml.AbstractBWCSerializationTestCase;
import org.elasticsearch.xpack.core.ml.dataframe.stats.common.FoldValuesTests;
import org.elasticsearch.xpack.core.ml.utils.ToXContentParams;
import org.junit.Before;
import java.io.IOException;
import java.util.Collections;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
public class ValidationLossTests extends AbstractBWCSerializationTestCase<ValidationLoss> {
@ -28,6 +35,11 @@ public class ValidationLossTests extends AbstractBWCSerializationTestCase<Valida
return ValidationLoss.fromXContent(parser, lenient);
}
@Override
protected ToXContent.Params getToXContentParams() {
return new ToXContent.MapParams(Collections.singletonMap(ToXContentParams.FOR_INTERNAL_STORAGE, "true"));
}
@Override
protected Writeable.Reader<ValidationLoss> instanceReader() {
return ValidationLoss::new;
@ -41,7 +53,7 @@ public class ValidationLossTests extends AbstractBWCSerializationTestCase<Valida
public static ValidationLoss createRandom() {
return new ValidationLoss(
randomAlphaOfLength(10),
randomList(5, () -> FoldValuesTests.createRandom())
randomList(5, FoldValuesTests::createRandom)
);
}
@ -49,4 +61,26 @@ public class ValidationLossTests extends AbstractBWCSerializationTestCase<Valida
protected ValidationLoss mutateInstanceForVersion(ValidationLoss instance, Version version) {
return instance;
}
public void testValidationLossForStats() {
String foldValuesFieldName = ValidationLoss.FOLD_VALUES.getPreferredName();
ValidationLoss validationLoss = createTestInstance();
// FOR_INTERNAL_STORAGE param defaults to "false", fold values *not* outputted
assertThat(Strings.toString(validationLoss), not(containsString(foldValuesFieldName)));
// FOR_INTERNAL_STORAGE param explicitly set to "false", fold values *not* outputted
assertThat(
Strings.toString(
validationLoss,
new ToXContent.MapParams(Collections.singletonMap(ToXContentParams.FOR_INTERNAL_STORAGE, "false"))),
not(containsString(foldValuesFieldName)));
// FOR_INTERNAL_STORAGE param explicitly set to "true", fold values are outputted
assertThat(
Strings.toString(
validationLoss,
new ToXContent.MapParams(Collections.singletonMap(ToXContentParams.FOR_INTERNAL_STORAGE, "true"))),
containsString(foldValuesFieldName));
}
}

View File

@ -16,7 +16,9 @@ import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfig;
import org.elasticsearch.xpack.ml.MachineLearning;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
@ -48,9 +50,15 @@ public class RestGetDataFrameAnalyticsStatsAction extends BaseRestHandler {
request.setPageParams(new PageParams(restRequest.paramAsInt(PageParams.FROM.getPreferredName(), PageParams.DEFAULT_FROM),
restRequest.paramAsInt(PageParams.SIZE.getPreferredName(), PageParams.DEFAULT_SIZE)));
}
request.setAllowNoMatch(restRequest.paramAsBoolean(GetDataFrameAnalyticsStatsAction.Request.ALLOW_NO_MATCH.getPreferredName(),
request.isAllowNoMatch()));
request.setAllowNoMatch(
restRequest.paramAsBoolean(
GetDataFrameAnalyticsStatsAction.Request.ALLOW_NO_MATCH.getPreferredName(), request.isAllowNoMatch()));
return channel -> client.execute(GetDataFrameAnalyticsStatsAction.INSTANCE, request, new RestToXContentListener<>(channel));
}
@Override
protected Set<String> responseParams() {
return Collections.singleton(GetDataFrameAnalyticsStatsAction.Response.VERBOSE);
}
}

View File

@ -43,6 +43,12 @@
"type":"int",
"description":"specifies a max number of analytics to get",
"default":100
},
"verbose":{
"type":"boolean",
"required":false,
"description":"whether the stats response should be verbose",
"default":false
}
}
}

View File

@ -903,7 +903,7 @@ setup:
- match: { data_frame_analytics.0.state: "stopped" }
---
"Test get stats on newly created congig":
"Test get stats on newly created config":
- do:
ml.put_data_frame_analytics:
@ -933,6 +933,20 @@ setup:
- match: { data_frame_analytics.0.memory_usage.peak_usage_bytes: 0 }
- match: { data_frame_analytics.0.memory_usage.status: "ok" }
- do:
ml.get_data_frame_analytics_stats:
id: "foo-1"
verbose: true
- match: { count: 1 }
- length: { data_frame_analytics: 1 }
- match: { data_frame_analytics.0.id: "foo-1" }
- match: { data_frame_analytics.0.state: "stopped" }
- match: { data_frame_analytics.0.data_counts.training_docs_count: 0 }
- match: { data_frame_analytics.0.data_counts.test_docs_count: 0 }
- match: { data_frame_analytics.0.data_counts.skipped_docs_count: 0 }
- match: { data_frame_analytics.0.memory_usage.peak_usage_bytes: 0 }
- match: { data_frame_analytics.0.memory_usage.status: "ok" }
---
"Test delete given stopped config":