Skip to content

Commit 0da3af7

Browse files
authored
[7.x] [ML] Add _cat/ml/data_frame/analytics API (#52260) (#52312)
1 parent ea6f0e3 commit 0da3af7

File tree

7 files changed

+390
-21
lines changed

7 files changed

+390
-21
lines changed

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@
254254
import org.elasticsearch.xpack.ml.rest.calendar.RestPostCalendarEventAction;
255255
import org.elasticsearch.xpack.ml.rest.calendar.RestPutCalendarAction;
256256
import org.elasticsearch.xpack.ml.rest.calendar.RestPutCalendarJobAction;
257+
import org.elasticsearch.xpack.ml.rest.cat.RestCatDataFrameAnalyticsAction;
257258
import org.elasticsearch.xpack.ml.rest.cat.RestCatDatafeedsAction;
258259
import org.elasticsearch.xpack.ml.rest.cat.RestCatJobsAction;
259260
import org.elasticsearch.xpack.ml.rest.cat.RestCatTrainedModelsAction;
@@ -788,7 +789,8 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
788789
// CAT Handlers
789790
new RestCatJobsAction(),
790791
new RestCatTrainedModelsAction(),
791-
new RestCatDatafeedsAction()
792+
new RestCatDatafeedsAction(),
793+
new RestCatDataFrameAnalyticsAction()
792794
);
793795
}
794796

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.ml.rest.cat;
7+
8+
import org.elasticsearch.client.node.NodeClient;
9+
import org.elasticsearch.cluster.metadata.MetaData;
10+
import org.elasticsearch.cluster.node.DiscoveryNode;
11+
import org.elasticsearch.common.Strings;
12+
import org.elasticsearch.common.Table;
13+
import org.elasticsearch.rest.RestRequest;
14+
import org.elasticsearch.rest.RestResponse;
15+
import org.elasticsearch.rest.action.RestActionListener;
16+
import org.elasticsearch.rest.action.RestResponseListener;
17+
import org.elasticsearch.rest.action.cat.AbstractCatAction;
18+
import org.elasticsearch.rest.action.cat.RestTable;
19+
import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsAction;
20+
import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsStatsAction;
21+
import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsStatsAction.Response.Stats;
22+
import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfig;
23+
import org.elasticsearch.xpack.core.ml.utils.PhaseProgress;
24+
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.function.Function;
28+
29+
import static java.util.Arrays.asList;
30+
import static java.util.Collections.unmodifiableList;
31+
import static java.util.stream.Collectors.joining;
32+
import static java.util.stream.Collectors.toMap;
33+
import static org.elasticsearch.rest.RestRequest.Method.GET;
34+
35+
public class RestCatDataFrameAnalyticsAction extends AbstractCatAction {
36+
37+
@Override
38+
public List<Route> routes() {
39+
return unmodifiableList(asList(
40+
new Route(GET, "_cat/ml/data_frame/analytics/{" + DataFrameAnalyticsConfig.ID.getPreferredName() + "}"),
41+
new Route(GET, "_cat/ml/data_frame/analytics")));
42+
}
43+
44+
@Override
45+
public String getName() {
46+
return "cat_ml_get_data_frame_analytics_action";
47+
}
48+
49+
@Override
50+
protected RestChannelConsumer doCatRequest(RestRequest restRequest, NodeClient client) {
51+
String dataFrameAnalyticsId = restRequest.param(DataFrameAnalyticsConfig.ID.getPreferredName());
52+
if (Strings.isNullOrEmpty(dataFrameAnalyticsId)) {
53+
dataFrameAnalyticsId = MetaData.ALL;
54+
}
55+
56+
GetDataFrameAnalyticsAction.Request getRequest = new GetDataFrameAnalyticsAction.Request(dataFrameAnalyticsId);
57+
getRequest.setAllowNoResources(
58+
restRequest.paramAsBoolean(
59+
GetDataFrameAnalyticsAction.Request.ALLOW_NO_MATCH.getPreferredName(), getRequest.isAllowNoResources()));
60+
61+
GetDataFrameAnalyticsStatsAction.Request getStatsRequest = new GetDataFrameAnalyticsStatsAction.Request(dataFrameAnalyticsId);
62+
getStatsRequest.setAllowNoMatch(true);
63+
64+
return channel -> client.execute(
65+
GetDataFrameAnalyticsAction.INSTANCE, getRequest, new RestActionListener<GetDataFrameAnalyticsAction.Response>(channel) {
66+
@Override
67+
public void processResponse(GetDataFrameAnalyticsAction.Response getResponse) {
68+
client.execute(
69+
GetDataFrameAnalyticsStatsAction.INSTANCE,
70+
getStatsRequest,
71+
new RestResponseListener<GetDataFrameAnalyticsStatsAction.Response>(channel) {
72+
@Override
73+
public RestResponse buildResponse(GetDataFrameAnalyticsStatsAction.Response getStatsResponse) throws Exception {
74+
return RestTable.buildResponse(buildTable(getResponse, getStatsResponse), channel);
75+
}
76+
});
77+
}
78+
});
79+
}
80+
81+
@Override
82+
protected void documentation(StringBuilder sb) {
83+
sb.append("/_cat/ml/data_frame/analytics\n");
84+
sb.append("/_cat/ml/data_frame/analytics/{").append(DataFrameAnalyticsConfig.ID.getPreferredName()).append("}\n");
85+
}
86+
87+
@Override
88+
protected Table getTableWithHeader(RestRequest unused) {
89+
return getTableWithHeader();
90+
}
91+
92+
private static Table getTableWithHeader() {
93+
return new Table()
94+
.startHeaders()
95+
// DFA config info
96+
.addCell("id", TableColumnAttributeBuilder.builder("the id").build())
97+
.addCell("type",
98+
TableColumnAttributeBuilder.builder("analysis type")
99+
.setAliases("t")
100+
.build())
101+
.addCell("create_time",
102+
TableColumnAttributeBuilder.builder("job creation time")
103+
.setAliases("ct", "createTime")
104+
.build())
105+
.addCell("version",
106+
TableColumnAttributeBuilder.builder("the version of Elasticsearch when the analytics was created", false)
107+
.setAliases("v")
108+
.build())
109+
.addCell("source_index",
110+
TableColumnAttributeBuilder.builder("source index", false)
111+
.setAliases("si", "sourceIndex")
112+
.build())
113+
.addCell("dest_index",
114+
TableColumnAttributeBuilder.builder("destination index", false)
115+
.setAliases("di", "destIndex")
116+
.build())
117+
.addCell("description",
118+
TableColumnAttributeBuilder.builder("description", false)
119+
.setAliases("d")
120+
.build())
121+
.addCell("model_memory_limit",
122+
TableColumnAttributeBuilder.builder("model memory limit", false)
123+
.setAliases("mml", "modelMemoryLimit")
124+
.build())
125+
// DFA stats info
126+
.addCell("state",
127+
TableColumnAttributeBuilder.builder("job state")
128+
.setAliases("s")
129+
.setTextAlignment(TableColumnAttributeBuilder.TextAlign.RIGHT)
130+
.build())
131+
.addCell("failure_reason",
132+
TableColumnAttributeBuilder.builder("failure reason", false)
133+
.setAliases("fr", "failureReason")
134+
.build())
135+
.addCell("progress",
136+
TableColumnAttributeBuilder.builder("progress", false)
137+
.setAliases("p")
138+
.build())
139+
.addCell("assignment_explanation",
140+
TableColumnAttributeBuilder.builder("why the job is or is not assigned to a node", false)
141+
.setAliases("ae", "assignmentExplanation")
142+
.build())
143+
// Node info
144+
.addCell("node.id",
145+
TableColumnAttributeBuilder.builder("id of the assigned node", false)
146+
.setAliases("ni", "nodeId")
147+
.build())
148+
.addCell("node.name",
149+
TableColumnAttributeBuilder.builder("name of the assigned node", false)
150+
.setAliases("nn", "nodeName")
151+
.build())
152+
.addCell("node.ephemeral_id",
153+
TableColumnAttributeBuilder.builder("ephemeral id of the assigned node", false)
154+
.setAliases("ne", "nodeEphemeralId")
155+
.build())
156+
.addCell("node.address",
157+
TableColumnAttributeBuilder.builder("network address of the assigned node", false)
158+
.setAliases("na", "nodeAddress")
159+
.build())
160+
.endHeaders();
161+
}
162+
163+
private static Table buildTable(GetDataFrameAnalyticsAction.Response getResponse,
164+
GetDataFrameAnalyticsStatsAction.Response getStatsResponse) {
165+
Map<String, Stats> statsById = getStatsResponse.getResponse().results().stream().collect(toMap(Stats::getId, Function.identity()));
166+
Table table = getTableWithHeader();
167+
for (DataFrameAnalyticsConfig config : getResponse.getResources().results()) {
168+
Stats stats = statsById.get(config.getId());
169+
DiscoveryNode node = stats == null ? null : stats.getNode();
170+
table
171+
.startRow()
172+
.addCell(config.getId())
173+
.addCell(config.getAnalysis().getWriteableName())
174+
.addCell(config.getCreateTime())
175+
.addCell(config.getVersion())
176+
.addCell(String.join(",", config.getSource().getIndex()))
177+
.addCell(config.getDest().getIndex())
178+
.addCell(config.getDescription())
179+
.addCell(config.getModelMemoryLimit())
180+
.addCell(stats == null ? null : stats.getState())
181+
.addCell(stats == null ? null : stats.getFailureReason())
182+
.addCell(stats == null ? null : progressToString(stats.getProgress()))
183+
.addCell(stats == null ? null : stats.getAssignmentExplanation())
184+
.addCell(node == null ? null : node.getId())
185+
.addCell(node == null ? null : node.getName())
186+
.addCell(node == null ? null : node.getEphemeralId())
187+
.addCell(node == null ? null : node.getAddress().toString())
188+
.endRow();
189+
}
190+
return table;
191+
}
192+
193+
private static String progressToString(List<PhaseProgress> phases) {
194+
return phases.stream().map(p -> p.getPhase() + ":" + p.getProgressPercent()).collect(joining(","));
195+
}
196+
}

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/cat/RestCatDatafeedsAction.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ protected Table getTableWithHeader(RestRequest request) {
7070
table.startHeaders();
7171

7272
// Datafeed Info
73-
table.addCell("id", TableColumnAttributeBuilder.builder().setDescription("the datafeed_id").build());
74-
table.addCell("state", TableColumnAttributeBuilder.builder()
75-
.setDescription("the datafeed state")
76-
.setAliases("s")
77-
.setTextAlignment(TableColumnAttributeBuilder.TextAlign.RIGHT)
78-
.build());
73+
table.addCell("id", TableColumnAttributeBuilder.builder("the datafeed_id").build());
74+
table.addCell("state",
75+
TableColumnAttributeBuilder.builder("the datafeed state")
76+
.setAliases("s")
77+
.setTextAlignment(TableColumnAttributeBuilder.TextAlign.RIGHT)
78+
.build());
7979
table.addCell("assignment_explanation",
8080
TableColumnAttributeBuilder.builder("why the datafeed is or is not assigned to a node", false)
8181
.setAliases("ae")

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/cat/RestCatJobsAction.java

+7-9
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,15 @@ protected Table getTableWithHeader(RestRequest request) {
7575
table.startHeaders();
7676

7777
// Job Info
78-
table.addCell("id", TableColumnAttributeBuilder.builder().setDescription("the job_id").build());
79-
table.addCell("state", TableColumnAttributeBuilder.builder()
80-
.setDescription("the job state")
81-
.setAliases("s")
82-
.setTextAlignment(TableColumnAttributeBuilder.TextAlign.RIGHT)
83-
.build());
78+
table.addCell("id", TableColumnAttributeBuilder.builder("the job_id").build());
79+
table.addCell("state",
80+
TableColumnAttributeBuilder.builder("the job state")
81+
.setAliases("s")
82+
.setTextAlignment(TableColumnAttributeBuilder.TextAlign.RIGHT)
83+
.build());
8484
table.addCell("opened_time",
85-
TableColumnAttributeBuilder.builder()
86-
.setDescription("the amount of time the job has been opened")
85+
TableColumnAttributeBuilder.builder("the amount of time the job has been opened", false)
8786
.setAliases("ot")
88-
.setDisplayByDefault(false)
8987
.build());
9088
table.addCell("assignment_explanation",
9189
TableColumnAttributeBuilder.builder("why the job is or is not assigned to a node", false)

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/cat/RestCatTrainedModelsAction.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,15 @@ protected Table getTableWithHeader(RestRequest request) {
130130
table.startHeaders();
131131

132132
// Trained Model Info
133-
table.addCell("id", TableColumnAttributeBuilder.builder().setDescription("the trained model id").build());
133+
table.addCell("id", TableColumnAttributeBuilder.builder("the trained model id").build());
134134
table.addCell("created_by", TableColumnAttributeBuilder.builder("who created the model", false)
135135
.setAliases("c", "createdBy")
136136
.setTextAlignment(TableColumnAttributeBuilder.TextAlign.RIGHT)
137137
.build());
138-
table.addCell("heap_size", TableColumnAttributeBuilder.builder()
139-
.setDescription("the estimated heap size to keep the model in memory")
138+
table.addCell("heap_size", TableColumnAttributeBuilder.builder("the estimated heap size to keep the model in memory")
140139
.setAliases("hs","modelHeapSize")
141140
.build());
142-
table.addCell("operations", TableColumnAttributeBuilder.builder()
143-
.setDescription("the estimated number of operations to use the model")
141+
table.addCell("operations", TableColumnAttributeBuilder.builder("the estimated number of operations to use the model")
144142
.setAliases("o", "modelOperations")
145143
.build());
146144
table.addCell("license", TableColumnAttributeBuilder.builder("The license level of the model", false)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"cat.ml_data_frame_analytics":{
3+
"documentation":{
4+
"url":"http://www.elastic.co/guide/en/elasticsearch/reference/current/get-dfanalytics-stats.html"
5+
},
6+
"stability":"stable",
7+
"url":{
8+
"paths":[
9+
{
10+
"path":"/_cat/ml/data_frame/analytics",
11+
"methods":[
12+
"GET"
13+
]
14+
},
15+
{
16+
"path":"/_cat/ml/data_frame/analytics/{id}",
17+
"methods":[
18+
"GET"
19+
],
20+
"parts":{
21+
"id":{
22+
"type":"string",
23+
"description":"The ID of the data frame analytics to fetch"
24+
}
25+
}
26+
}
27+
]
28+
},
29+
"params":{
30+
"allow_no_match":{
31+
"type":"boolean",
32+
"required":false,
33+
"description":"Whether to ignore if a wildcard expression matches no configs. (This includes `_all` string or when no configs have been specified)"
34+
},
35+
"bytes":{
36+
"type":"enum",
37+
"description":"The unit in which to display byte values",
38+
"options":[
39+
"b",
40+
"k",
41+
"kb",
42+
"m",
43+
"mb",
44+
"g",
45+
"gb",
46+
"t",
47+
"tb",
48+
"p",
49+
"pb"
50+
]
51+
},
52+
"format":{
53+
"type":"string",
54+
"description":"a short version of the Accept header, e.g. json, yaml"
55+
},
56+
"h":{
57+
"type":"list",
58+
"description":"Comma-separated list of column names to display"
59+
},
60+
"help":{
61+
"type":"boolean",
62+
"description":"Return help information",
63+
"default":false
64+
},
65+
"s":{
66+
"type":"list",
67+
"description":"Comma-separated list of column names or column aliases to sort by"
68+
},
69+
"time":{
70+
"type":"enum",
71+
"description":"The unit in which to display time values",
72+
"options":[
73+
"d (Days)",
74+
"h (Hours)",
75+
"m (Minutes)",
76+
"s (Seconds)",
77+
"ms (Milliseconds)",
78+
"micros (Microseconds)",
79+
"nanos (Nanoseconds)"
80+
]
81+
},
82+
"v":{
83+
"type":"boolean",
84+
"description":"Verbose mode. Display column headers",
85+
"default":false
86+
}
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)