Skip to content

Commit ebbfddc

Browse files
committed
HLRC: Adding ML Job stats (#33183)
* HLRC: Adding pojos for get job stats HLRC: Adding pojos for job stats request * HLRC: Adding job stats pojos * HLRC: ML job stats * Minor syntax changes and adding license headers * minor comment change * Moving to client package, minor changes * Addressing PR comments * removing bad sleep * addressing minor comment around test methods * adding toplevel random fields for tests * addressing minor review comments
1 parent 4dc899c commit ebbfddc

21 files changed

+1596
-2
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java

+18
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.client.ml.DeleteJobRequest;
2929
import org.elasticsearch.client.ml.GetBucketsRequest;
3030
import org.elasticsearch.client.ml.GetJobRequest;
31+
import org.elasticsearch.client.ml.GetJobStatsRequest;
3132
import org.elasticsearch.client.ml.GetRecordsRequest;
3233
import org.elasticsearch.client.ml.OpenJobRequest;
3334
import org.elasticsearch.client.ml.PutJobRequest;
@@ -126,6 +127,23 @@ static Request getBuckets(GetBucketsRequest getBucketsRequest) throws IOExceptio
126127
return request;
127128
}
128129

130+
static Request getJobStats(GetJobStatsRequest getJobStatsRequest) {
131+
String endpoint = new EndpointBuilder()
132+
.addPathPartAsIs("_xpack")
133+
.addPathPartAsIs("ml")
134+
.addPathPartAsIs("anomaly_detectors")
135+
.addPathPart(Strings.collectionToCommaDelimitedString(getJobStatsRequest.getJobIds()))
136+
.addPathPartAsIs("_stats")
137+
.build();
138+
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
139+
140+
RequestConverters.Params params = new RequestConverters.Params(request);
141+
if (getJobStatsRequest.isAllowNoJobs() != null) {
142+
params.putParam("allow_no_jobs", Boolean.toString(getJobStatsRequest.isAllowNoJobs()));
143+
}
144+
return request;
145+
}
146+
129147
static Request getRecords(GetRecordsRequest getRecordsRequest) throws IOException {
130148
String endpoint = new EndpointBuilder()
131149
.addPathPartAsIs("_xpack")

client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java

+44
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
package org.elasticsearch.client;
2020

2121
import org.elasticsearch.action.ActionListener;
22+
import org.elasticsearch.client.ml.GetJobStatsRequest;
23+
import org.elasticsearch.client.ml.GetJobStatsResponse;
24+
import org.elasticsearch.client.ml.job.stats.JobStats;
2225
import org.elasticsearch.client.ml.CloseJobRequest;
2326
import org.elasticsearch.client.ml.CloseJobResponse;
2427
import org.elasticsearch.client.ml.DeleteJobRequest;
@@ -288,6 +291,47 @@ public void getBucketsAsync(GetBucketsRequest request, RequestOptions options, A
288291
Collections.emptySet());
289292
}
290293

294+
/**
295+
* Gets usage statistics for one or more Machine Learning jobs
296+
*
297+
* <p>
298+
* For additional info
299+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job-stats.html">Get Job stats docs</a>
300+
* </p>
301+
* @param request {@link GetJobStatsRequest} Request containing a list of jobId(s) and additional options
302+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
303+
* @return {@link GetJobStatsResponse} response object containing
304+
* the {@link JobStats} objects and the number of jobs found
305+
* @throws IOException when there is a serialization issue sending the request or receiving the response
306+
*/
307+
public GetJobStatsResponse getJobStats(GetJobStatsRequest request, RequestOptions options) throws IOException {
308+
return restHighLevelClient.performRequestAndParseEntity(request,
309+
MLRequestConverters::getJobStats,
310+
options,
311+
GetJobStatsResponse::fromXContent,
312+
Collections.emptySet());
313+
}
314+
315+
/**
316+
* Gets one or more Machine Learning job configuration info, asynchronously.
317+
*
318+
* <p>
319+
* For additional info
320+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job-stats.html">Get Job stats docs</a>
321+
* </p>
322+
* @param request {@link GetJobStatsRequest} Request containing a list of jobId(s) and additional options
323+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
324+
* @param listener Listener to be notified with {@link GetJobStatsResponse} upon request completion
325+
*/
326+
public void getJobStatsAsync(GetJobStatsRequest request, RequestOptions options, ActionListener<GetJobStatsResponse> listener) {
327+
restHighLevelClient.performRequestAsyncAndParseEntity(request,
328+
MLRequestConverters::getJobStats,
329+
options,
330+
GetJobStatsResponse::fromXContent,
331+
listener,
332+
Collections.emptySet());
333+
}
334+
291335
/**
292336
* Gets the records for a Machine Learning Job.
293337
* <p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client.ml;
20+
21+
import org.elasticsearch.action.ActionRequest;
22+
import org.elasticsearch.action.ActionRequestValidationException;
23+
import org.elasticsearch.client.ml.job.config.Job;
24+
import org.elasticsearch.common.ParseField;
25+
import org.elasticsearch.common.Strings;
26+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
27+
import org.elasticsearch.common.xcontent.ObjectParser;
28+
import org.elasticsearch.common.xcontent.ToXContentObject;
29+
import org.elasticsearch.common.xcontent.XContentBuilder;
30+
31+
import java.io.IOException;
32+
import java.util.ArrayList;
33+
import java.util.Arrays;
34+
import java.util.List;
35+
import java.util.Objects;
36+
37+
38+
/**
39+
* Request object to get {@link org.elasticsearch.client.ml.job.stats.JobStats} by their respective jobIds
40+
*
41+
* `_all` explicitly gets all the jobs' statistics in the cluster
42+
* An empty request (no `jobId`s) implicitly gets all the jobs' statistics in the cluster
43+
*/
44+
public class GetJobStatsRequest extends ActionRequest implements ToXContentObject {
45+
46+
public static final ParseField ALLOW_NO_JOBS = new ParseField("allow_no_jobs");
47+
48+
@SuppressWarnings("unchecked")
49+
public static final ConstructingObjectParser<GetJobStatsRequest, Void> PARSER = new ConstructingObjectParser<>(
50+
"get_jobs_stats_request", a -> new GetJobStatsRequest((List<String>) a[0]));
51+
52+
static {
53+
PARSER.declareField(ConstructingObjectParser.constructorArg(),
54+
p -> Arrays.asList(Strings.commaDelimitedListToStringArray(p.text())),
55+
Job.ID, ObjectParser.ValueType.STRING_ARRAY);
56+
PARSER.declareBoolean(GetJobStatsRequest::setAllowNoJobs, ALLOW_NO_JOBS);
57+
}
58+
59+
private static final String ALL_JOBS = "_all";
60+
61+
private final List<String> jobIds;
62+
private Boolean allowNoJobs;
63+
64+
/**
65+
* Explicitly gets all jobs statistics
66+
*
67+
* @return a {@link GetJobStatsRequest} for all existing jobs
68+
*/
69+
public static GetJobStatsRequest getAllJobStatsRequest(){
70+
return new GetJobStatsRequest(ALL_JOBS);
71+
}
72+
73+
GetJobStatsRequest(List<String> jobIds) {
74+
if (jobIds.stream().anyMatch(Objects::isNull)) {
75+
throw new NullPointerException("jobIds must not contain null values");
76+
}
77+
this.jobIds = new ArrayList<>(jobIds);
78+
}
79+
80+
/**
81+
* Get the specified Job's statistics via their unique jobIds
82+
*
83+
* @param jobIds must be non-null and each jobId must be non-null
84+
*/
85+
public GetJobStatsRequest(String... jobIds) {
86+
this(Arrays.asList(jobIds));
87+
}
88+
89+
/**
90+
* All the jobIds for which to get statistics
91+
*/
92+
public List<String> getJobIds() {
93+
return jobIds;
94+
}
95+
96+
public Boolean isAllowNoJobs() {
97+
return this.allowNoJobs;
98+
}
99+
100+
/**
101+
* Whether to ignore if a wildcard expression matches no jobs.
102+
*
103+
* This includes `_all` string or when no jobs have been specified
104+
*
105+
* @param allowNoJobs When {@code true} ignore if wildcard or `_all` matches no jobs. Defaults to {@code true}
106+
*/
107+
public void setAllowNoJobs(boolean allowNoJobs) {
108+
this.allowNoJobs = allowNoJobs;
109+
}
110+
111+
@Override
112+
public int hashCode() {
113+
return Objects.hash(jobIds, allowNoJobs);
114+
}
115+
116+
@Override
117+
public boolean equals(Object other) {
118+
if (this == other) {
119+
return true;
120+
}
121+
122+
if (other == null || getClass() != other.getClass()) {
123+
return false;
124+
}
125+
126+
GetJobStatsRequest that = (GetJobStatsRequest) other;
127+
return Objects.equals(jobIds, that.jobIds) &&
128+
Objects.equals(allowNoJobs, that.allowNoJobs);
129+
}
130+
131+
@Override
132+
public ActionRequestValidationException validate() {
133+
return null;
134+
}
135+
136+
@Override
137+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
138+
builder.startObject();
139+
builder.field(Job.ID.getPreferredName(), Strings.collectionToCommaDelimitedString(jobIds));
140+
if (allowNoJobs != null) {
141+
builder.field(ALLOW_NO_JOBS.getPreferredName(), allowNoJobs);
142+
}
143+
builder.endObject();
144+
return builder;
145+
}
146+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client.ml;
20+
21+
import org.elasticsearch.common.ParseField;
22+
import org.elasticsearch.common.Strings;
23+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
24+
import org.elasticsearch.common.xcontent.XContentParser;
25+
import org.elasticsearch.client.ml.job.stats.JobStats;
26+
27+
import java.io.IOException;
28+
import java.util.List;
29+
import java.util.Objects;
30+
31+
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
32+
33+
/**
34+
* Contains a {@link List} of the found {@link JobStats} objects and the total count found
35+
*/
36+
public class GetJobStatsResponse extends AbstractResultResponse<JobStats> {
37+
38+
public static final ParseField RESULTS_FIELD = new ParseField("jobs");
39+
40+
@SuppressWarnings("unchecked")
41+
public static final ConstructingObjectParser<GetJobStatsResponse, Void> PARSER =
42+
new ConstructingObjectParser<>("jobs_stats_response", true,
43+
a -> new GetJobStatsResponse((List<JobStats>) a[0], (long) a[1]));
44+
45+
static {
46+
PARSER.declareObjectArray(constructorArg(), JobStats.PARSER, RESULTS_FIELD);
47+
PARSER.declareLong(constructorArg(), COUNT);
48+
}
49+
50+
GetJobStatsResponse(List<JobStats> jobStats, long count) {
51+
super(RESULTS_FIELD, jobStats, count);
52+
}
53+
54+
/**
55+
* The collection of {@link JobStats} objects found in the query
56+
*/
57+
public List<JobStats> jobStats() {
58+
return results;
59+
}
60+
61+
public static GetJobStatsResponse fromXContent(XContentParser parser) throws IOException {
62+
return PARSER.parse(parser, null);
63+
}
64+
65+
@Override
66+
public int hashCode() {
67+
return Objects.hash(results, count);
68+
}
69+
70+
@Override
71+
public boolean equals(Object obj) {
72+
if (this == obj) {
73+
return true;
74+
}
75+
76+
if (obj == null || getClass() != obj.getClass()) {
77+
return false;
78+
}
79+
80+
GetJobStatsResponse other = (GetJobStatsResponse) obj;
81+
return Objects.equals(results, other.results) && count == other.count;
82+
}
83+
84+
@Override
85+
public final String toString() {
86+
return Strings.toString(this);
87+
}
88+
}

0 commit comments

Comments
 (0)