Skip to content

Commit 2dc872f

Browse files
authored
EQL: Add HLRC for EQL stats (#53043) (#53148)
1 parent 360ac19 commit 2dc872f

File tree

9 files changed

+395
-6
lines changed

9 files changed

+395
-6
lines changed

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.elasticsearch.action.ActionListener;
2323
import org.elasticsearch.client.eql.EqlSearchRequest;
2424
import org.elasticsearch.client.eql.EqlSearchResponse;
25+
import org.elasticsearch.client.eql.EqlStatsRequest;
26+
import org.elasticsearch.client.eql.EqlStatsResponse;
2527

2628
import java.io.IOException;
2729
import java.util.Collections;
@@ -85,4 +87,42 @@ public Cancellable searchAsync(EqlSearchRequest request,
8587
Collections.emptySet()
8688
);
8789
}
90+
91+
/**
92+
* Get the eql stats
93+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-stats.html">
94+
* the docs</a> for more.
95+
* @param request the request
96+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
97+
* @return the response
98+
* @throws IOException in case there is a problem sending the request or parsing back the response
99+
*/
100+
public EqlStatsResponse stats(EqlStatsRequest request, RequestOptions options) throws IOException {
101+
return restHighLevelClient.performRequestAndParseEntity(
102+
request,
103+
EqlRequestConverters::stats,
104+
options,
105+
EqlStatsResponse::fromXContent,
106+
Collections.emptySet()
107+
);
108+
}
109+
110+
/**
111+
* Asynchronously get the eql stats
112+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-stats.html">
113+
* the docs</a> for more.
114+
* @param request the request
115+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
116+
* @param listener the listener to be notified upon request completion
117+
* @return cancellable that may be used to cancel the request
118+
*/
119+
public Cancellable statsAsync(EqlStatsRequest request, RequestOptions options, ActionListener<EqlStatsResponse> listener) {
120+
return restHighLevelClient.performRequestAsyncAndParseEntity(request,
121+
EqlRequestConverters::stats,
122+
options,
123+
EqlStatsResponse::fromXContent,
124+
listener,
125+
Collections.emptySet()
126+
);
127+
}
88128
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.http.client.methods.HttpGet;
2323
import org.elasticsearch.client.eql.EqlSearchRequest;
24+
import org.elasticsearch.client.eql.EqlStatsRequest;
2425

2526
import java.io.IOException;
2627

@@ -41,4 +42,11 @@ static Request search(EqlSearchRequest eqlSearchRequest) throws IOException {
4142
request.addParameters(parameters.asMap());
4243
return request;
4344
}
45+
46+
static Request stats(EqlStatsRequest eqlStatsRequest) throws IOException {
47+
String endpoint = new RequestConverters.EndpointBuilder()
48+
.addPathPartAsIs("_eql", "stats")
49+
.build();
50+
return new Request(HttpGet.METHOD_NAME, endpoint);
51+
}
4452
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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.eql;
20+
21+
import org.elasticsearch.client.Validatable;
22+
23+
public final class EqlStatsRequest implements Validatable {
24+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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.eql;
20+
21+
import org.elasticsearch.client.NodesResponseHeader;
22+
import org.elasticsearch.common.ParseField;
23+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
24+
import org.elasticsearch.common.xcontent.XContentParser;
25+
26+
import java.io.IOException;
27+
import java.util.List;
28+
import java.util.Map;
29+
import java.util.Objects;
30+
31+
public class EqlStatsResponse {
32+
private final NodesResponseHeader header;
33+
private final String clusterName;
34+
private final List<Node> nodes;
35+
36+
public EqlStatsResponse(NodesResponseHeader header, String clusterName, List<Node> nodes) {
37+
this.header = header;
38+
this.clusterName = clusterName;
39+
this.nodes = nodes;
40+
}
41+
42+
@SuppressWarnings("unchecked")
43+
private static final ConstructingObjectParser<EqlStatsResponse, Void>
44+
PARSER = new ConstructingObjectParser<>("eql/stats_response", true, args -> {
45+
int i = 0;
46+
NodesResponseHeader header = (NodesResponseHeader) args[i++];
47+
String clusterName = (String) args[i++];
48+
List<Node> nodes = (List<Node>) args[i];
49+
return new EqlStatsResponse(header, clusterName, nodes);
50+
});
51+
52+
static {
53+
PARSER.declareObject(ConstructingObjectParser.constructorArg(), NodesResponseHeader::fromXContent, new ParseField("_nodes"));
54+
PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("cluster_name"));
55+
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
56+
(p, c) -> EqlStatsResponse.Node.PARSER.apply(p, null),
57+
new ParseField("stats"));
58+
}
59+
60+
public static EqlStatsResponse fromXContent(XContentParser parser) throws IOException {
61+
return PARSER.parse(parser, null);
62+
}
63+
64+
public NodesResponseHeader getHeader() {
65+
return header;
66+
}
67+
68+
public List<Node> getNodes() {
69+
return nodes;
70+
}
71+
72+
public String getClusterName() {
73+
return clusterName;
74+
}
75+
76+
@Override
77+
public boolean equals(Object o) {
78+
if (this == o) return true;
79+
if (o == null || getClass() != o.getClass()) return false;
80+
EqlStatsResponse that = (EqlStatsResponse) o;
81+
return Objects.equals(nodes, that.nodes) && Objects.equals(header, that.header) && Objects.equals(clusterName, that.clusterName);
82+
}
83+
84+
@Override
85+
public int hashCode() {
86+
return Objects.hash(nodes, header, clusterName);
87+
}
88+
89+
public static class Node {
90+
@SuppressWarnings("unchecked")
91+
public static final ConstructingObjectParser<Node, Void>
92+
PARSER = new ConstructingObjectParser<>("eql/stats_response_node", true, (args, c) -> new Node((Map<String, Object>) args[0]));
93+
94+
static {
95+
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> p.map(), new ParseField("stats"));
96+
}
97+
98+
private Map<String, Object> stats;
99+
100+
public Node(Map<String, Object> stats) {
101+
this.stats = stats;
102+
}
103+
104+
public Map<String, Object> getStats() {
105+
return stats;
106+
}
107+
108+
@Override
109+
public boolean equals(Object o) {
110+
if (this == o) return true;
111+
if (o == null || getClass() != o.getClass()) return false;
112+
Node node = (Node) o;
113+
return Objects.equals(stats, node.stats);
114+
}
115+
116+
@Override
117+
public int hashCode() {
118+
return Objects.hash(stats);
119+
}
120+
}
121+
}

client/rest-high-level/src/test/java/org/elasticsearch/client/EqlIT.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.apache.http.client.methods.HttpPut;
2424
import org.elasticsearch.client.eql.EqlSearchRequest;
2525
import org.elasticsearch.client.eql.EqlSearchResponse;
26+
import org.elasticsearch.client.eql.EqlStatsRequest;
27+
import org.elasticsearch.client.eql.EqlStatsResponse;
2628
import org.elasticsearch.common.settings.Settings;
2729
import org.elasticsearch.common.time.DateUtils;
2830
import org.elasticsearch.index.IndexSettings;
@@ -31,6 +33,7 @@
3133
import java.time.format.DateTimeFormatter;
3234

3335
import static org.hamcrest.Matchers.equalTo;
36+
import static org.hamcrest.Matchers.greaterThan;
3437

3538
public class EqlIT extends ESRestHighLevelClientTestCase {
3639

@@ -97,4 +100,16 @@ public void testLargeMapping() throws Exception {
97100
assertNotNull(response.hits());
98101
assertThat(response.hits().events().size(), equalTo(1));
99102
}
103+
104+
// Basic test for stats
105+
// TODO: add more tests once the stats are hooked up
106+
public void testStats() throws Exception {
107+
EqlClient eql = highLevelClient().eql();
108+
EqlStatsRequest request = new EqlStatsRequest();
109+
EqlStatsResponse response = execute(request, eql::stats, eql::statsAsync);
110+
assertNotNull(response);
111+
assertNotNull(response.getHeader());
112+
assertThat(response.getHeader().getTotal(), greaterThan(0));
113+
assertThat(response.getNodes().size(), greaterThan(0));
114+
}
100115
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
20+
package org.elasticsearch.client.eql;
21+
22+
import org.elasticsearch.client.AbstractResponseTestCase;
23+
import org.elasticsearch.client.NodesResponseHeader;
24+
import org.elasticsearch.common.xcontent.XContentParser;
25+
import org.elasticsearch.common.xcontent.XContentType;
26+
27+
import java.io.IOException;
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.HashMap;
31+
import java.util.List;
32+
import java.util.Map;
33+
34+
import static org.hamcrest.Matchers.is;
35+
36+
public class EqlStatsResponseTests extends AbstractResponseTestCase<EqlStatsResponseToXContent, EqlStatsResponse> {
37+
38+
private static Map<String, Object> buildRandomCountersMap(int count) {
39+
Map<String, Object> map = new HashMap<>();
40+
for (int i = 0; i < count; i++) {
41+
map.put(randomAlphaOfLength(10), randomIntBetween(0, Integer.MAX_VALUE));
42+
}
43+
return map;
44+
}
45+
46+
private static Map<String, Object> buildRandomNodeStats(int featuresNumber) {
47+
Map<String, Object> stats = new HashMap<>();
48+
49+
int countersNumber = randomIntBetween(0, 10);
50+
51+
Map<String, Object> features = new HashMap<>();
52+
for (int i = 0; i < featuresNumber; i++) {
53+
features.put(randomAlphaOfLength(10), buildRandomCountersMap(countersNumber));
54+
}
55+
56+
stats.put("features", features);
57+
58+
Map<String, Object> res = new HashMap<>();
59+
res.put("stats", stats);
60+
return res;
61+
}
62+
63+
@Override
64+
protected EqlStatsResponseToXContent createServerTestInstance(XContentType xContentType) {
65+
NodesResponseHeader header = new NodesResponseHeader(randomInt(10), randomInt(10),
66+
randomInt(10), Collections.emptyList());
67+
String clusterName = randomAlphaOfLength(10);
68+
69+
int nodeCount = randomInt(10);
70+
int featuresNumber = randomIntBetween(0, 10);
71+
List<EqlStatsResponse.Node> nodes = new ArrayList<>(nodeCount);
72+
for (int i = 0; i < nodeCount; i++) {
73+
Map<String, Object> stat = buildRandomNodeStats(featuresNumber);
74+
nodes.add(new EqlStatsResponse.Node(stat));
75+
}
76+
EqlStatsResponse response = new EqlStatsResponse(header, clusterName, nodes);
77+
return new EqlStatsResponseToXContent(new EqlStatsResponse(header, clusterName, nodes));
78+
}
79+
80+
@Override
81+
protected EqlStatsResponse doParseToClientInstance(XContentParser parser) throws IOException {
82+
return EqlStatsResponse.fromXContent(parser);
83+
}
84+
85+
@Override
86+
protected void assertInstances(EqlStatsResponseToXContent serverTestInstanceWrap, EqlStatsResponse clientInstance) {
87+
EqlStatsResponse serverTestInstance = serverTestInstanceWrap.unwrap();
88+
assertThat(serverTestInstance, is(clientInstance));
89+
}
90+
}

0 commit comments

Comments
 (0)