diff --git a/core/src/main/java/org/elasticsearch/rest/BaseRestHandler.java b/core/src/main/java/org/elasticsearch/rest/BaseRestHandler.java index 3bb6c6773b9..f1804ee01b1 100644 --- a/core/src/main/java/org/elasticsearch/rest/BaseRestHandler.java +++ b/core/src/main/java/org/elasticsearch/rest/BaseRestHandler.java @@ -71,49 +71,58 @@ public abstract class BaseRestHandler extends AbstractComponent implements RestH request.unconsumedParams().stream().filter(p -> !responseParams().contains(p)).collect(Collectors.toCollection(TreeSet::new)); // validate the non-response params - if (unconsumedParams.isEmpty() == false) { - String message = String.format( - Locale.ROOT, - "request [%s] contains unrecognized parameter%s: ", - request.path(), - unconsumedParams.size() > 1 ? "s" : ""); - boolean first = true; - for (final String unconsumedParam : unconsumedParams) { - final LevensteinDistance ld = new LevensteinDistance(); - final List> scoredParams = new ArrayList<>(); - final Set candidateParams = new HashSet<>(); - candidateParams.addAll(request.consumedParams()); - candidateParams.addAll(responseParams()); - for (final String candidateParam : candidateParams) { - final float distance = ld.getDistance(unconsumedParam, candidateParam); - if (distance > 0.5f) { - scoredParams.add(new Tuple<>(distance, candidateParam)); - } - } - CollectionUtil.timSort(scoredParams, (a, b) -> { - // sort by distance in reverse order, then parameter name for equal distances - int compare = a.v1().compareTo(b.v1()); - if (compare != 0) return -compare; - else return a.v2().compareTo(b.v2()); - }); - if (first == false) { - message += ", "; - } - message += "[" + unconsumedParam + "]"; - final List keys = scoredParams.stream().map(Tuple::v2).collect(Collectors.toList()); - if (keys.isEmpty() == false) { - message += " -> did you mean " + (keys.size() == 1 ? "[" + keys.get(0) + "]": "any of " + keys.toString()) + "?"; - } - first = false; - } - - throw new IllegalArgumentException(message); + if (!unconsumedParams.isEmpty()) { + final Set candidateParams = new HashSet<>(); + candidateParams.addAll(request.consumedParams()); + candidateParams.addAll(responseParams()); + throw new IllegalArgumentException(unrecognized(request, unconsumedParams, candidateParams, "parameter")); } // execute the action action.accept(channel); } + protected final String unrecognized( + final RestRequest request, + final Set invalids, + final Set candidates, + final String detail) { + String message = String.format( + Locale.ROOT, + "request [%s] contains unrecognized %s%s: ", + request.path(), + detail, + invalids.size() > 1 ? "s" : ""); + boolean first = true; + for (final String invalid : invalids) { + final LevensteinDistance ld = new LevensteinDistance(); + final List> scoredParams = new ArrayList<>(); + for (final String candidate : candidates) { + final float distance = ld.getDistance(invalid, candidate); + if (distance > 0.5f) { + scoredParams.add(new Tuple<>(distance, candidate)); + } + } + CollectionUtil.timSort(scoredParams, (a, b) -> { + // sort by distance in reverse order, then parameter name for equal distances + int compare = a.v1().compareTo(b.v1()); + if (compare != 0) return -compare; + else return a.v2().compareTo(b.v2()); + }); + if (first == false) { + message += ", "; + } + message += "[" + invalid + "]"; + final List keys = scoredParams.stream().map(Tuple::v2).collect(Collectors.toList()); + if (keys.isEmpty() == false) { + message += " -> did you mean " + (keys.size() == 1 ? "[" + keys.get(0) + "]" : "any of " + keys.toString()) + "?"; + } + first = false; + } + + return message; + } + /** * REST requests are handled by preparing a channel consumer that represents the execution of * the request against a channel. diff --git a/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesInfoAction.java b/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesInfoAction.java index 40cfc6372a3..d4da4fc931d 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesInfoAction.java +++ b/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesInfoAction.java @@ -55,7 +55,7 @@ public class RestNodesInfoAction extends BaseRestHandler { public RestNodesInfoAction(Settings settings, RestController controller, SettingsFilter settingsFilter) { super(settings); controller.registerHandler(GET, "/_nodes", this); - // this endpoint is used for metrics, not for nodeIds, like /_nodes/fs + // this endpoint is used for metrics, not for node IDs, like /_nodes/fs controller.registerHandler(GET, "/_nodes/{nodeId}", this); controller.registerHandler(GET, "/_nodes/{nodeId}/{metrics}", this); // added this endpoint to be aligned with stats diff --git a/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesStatsAction.java b/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesStatsAction.java index 917f5b2c5b1..93094844025 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesStatsAction.java +++ b/core/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesStatsAction.java @@ -33,7 +33,13 @@ import org.elasticsearch.rest.action.RestActions.NodesResponseRestListener; import java.io.IOException; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; import java.util.Set; +import java.util.TreeSet; +import java.util.function.Consumer; import static org.elasticsearch.rest.RestRequest.Method.GET; @@ -48,9 +54,38 @@ public class RestNodesStatsAction extends BaseRestHandler { controller.registerHandler(GET, "/_nodes/stats/{metric}", this); controller.registerHandler(GET, "/_nodes/{nodeId}/stats/{metric}", this); - controller.registerHandler(GET, "/_nodes/stats/{metric}/{indexMetric}", this); + controller.registerHandler(GET, "/_nodes/stats/{metric}/{index_metric}", this); - controller.registerHandler(GET, "/_nodes/{nodeId}/stats/{metric}/{indexMetric}", this); + controller.registerHandler(GET, "/_nodes/{nodeId}/stats/{metric}/{index_metric}", this); + } + + static final Map> METRICS; + + static { + final Map> metrics = new HashMap<>(); + metrics.put("os", r -> r.os(true)); + metrics.put("jvm", r -> r.jvm(true)); + metrics.put("thread_pool", r -> r.threadPool(true)); + metrics.put("fs", r -> r.fs(true)); + metrics.put("transport", r -> r.transport(true)); + metrics.put("http", r -> r.http(true)); + metrics.put("indices", r -> r.indices(true)); + metrics.put("process", r -> r.process(true)); + metrics.put("breaker", r -> r.breaker(true)); + metrics.put("script", r -> r.script(true)); + metrics.put("discovery", r -> r.discovery(true)); + metrics.put("ingest", r -> r.ingest(true)); + METRICS = Collections.unmodifiableMap(metrics); + } + + static final Map> FLAGS; + + static { + final Map> flags = new HashMap<>(); + for (final Flag flag : CommonStatsFlags.Flag.values()) { + flags.put(flag.getRestName(), f -> f.set(flag, true)); + } + FLAGS = Collections.unmodifiableMap(flags); } @Override @@ -62,35 +97,72 @@ public class RestNodesStatsAction extends BaseRestHandler { nodesStatsRequest.timeout(request.param("timeout")); if (metrics.size() == 1 && metrics.contains("_all")) { + if (request.hasParam("index_metric")) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "request [%s] contains index metrics [%s] but all stats requested", + request.path(), + request.param("index_metric"))); + } nodesStatsRequest.all(); nodesStatsRequest.indices(CommonStatsFlags.ALL); + } else if (metrics.contains("_all")) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, + "request [%s] contains _all and individual metrics [%s]", + request.path(), + request.param("metric"))); } else { nodesStatsRequest.clear(); - nodesStatsRequest.os(metrics.contains("os")); - nodesStatsRequest.jvm(metrics.contains("jvm")); - nodesStatsRequest.threadPool(metrics.contains("thread_pool")); - nodesStatsRequest.fs(metrics.contains("fs")); - nodesStatsRequest.transport(metrics.contains("transport")); - nodesStatsRequest.http(metrics.contains("http")); - nodesStatsRequest.indices(metrics.contains("indices")); - nodesStatsRequest.process(metrics.contains("process")); - nodesStatsRequest.breaker(metrics.contains("breaker")); - nodesStatsRequest.script(metrics.contains("script")); - nodesStatsRequest.discovery(metrics.contains("discovery")); - nodesStatsRequest.ingest(metrics.contains("ingest")); + + // use a sorted set so the unrecognized parameters appear in a reliable sorted order + final Set invalidMetrics = new TreeSet<>(); + for (final String metric : metrics) { + final Consumer handler = METRICS.get(metric); + if (handler != null) { + handler.accept(nodesStatsRequest); + } else { + invalidMetrics.add(metric); + } + } + + if (!invalidMetrics.isEmpty()) { + throw new IllegalArgumentException(unrecognized(request, invalidMetrics, METRICS.keySet(), "metric")); + } // check for index specific metrics if (metrics.contains("indices")) { - Set indexMetrics = Strings.splitStringByCommaToSet(request.param("indexMetric", "_all")); + Set indexMetrics = Strings.splitStringByCommaToSet(request.param("index_metric", "_all")); if (indexMetrics.size() == 1 && indexMetrics.contains("_all")) { nodesStatsRequest.indices(CommonStatsFlags.ALL); } else { CommonStatsFlags flags = new CommonStatsFlags(); - for (Flag flag : CommonStatsFlags.Flag.values()) { - flags.set(flag, indexMetrics.contains(flag.getRestName())); + flags.clear(); + // use a sorted set so the unrecognized parameters appear in a reliable sorted order + final Set invalidIndexMetrics = new TreeSet<>(); + for (final String indexMetric : indexMetrics) { + final Consumer handler = FLAGS.get(indexMetric); + if (handler != null) { + handler.accept(flags); + } else { + invalidIndexMetrics.add(indexMetric); + } } + + if (!invalidIndexMetrics.isEmpty()) { + throw new IllegalArgumentException(unrecognized(request, invalidIndexMetrics, FLAGS.keySet(), "index metric")); + } + nodesStatsRequest.indices(flags); } + } else if (request.hasParam("index_metric")) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "request [%s] contains index metrics [%s] but indices stats not requested", + request.path(), + request.param("index_metric"))); } } diff --git a/core/src/main/java/org/elasticsearch/rest/action/admin/indices/RestIndicesStatsAction.java b/core/src/main/java/org/elasticsearch/rest/action/admin/indices/RestIndicesStatsAction.java index e7336225c5e..041a15b4119 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/admin/indices/RestIndicesStatsAction.java +++ b/core/src/main/java/org/elasticsearch/rest/action/admin/indices/RestIndicesStatsAction.java @@ -36,7 +36,13 @@ import org.elasticsearch.rest.action.RestBuilderListener; import java.io.IOException; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; import java.util.Set; +import java.util.TreeSet; +import java.util.function.Consumer; import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestStatus.OK; @@ -49,11 +55,34 @@ public class RestIndicesStatsAction extends BaseRestHandler { super(settings); controller.registerHandler(GET, "/_stats", this); controller.registerHandler(GET, "/_stats/{metric}", this); - controller.registerHandler(GET, "/_stats/{metric}/{indexMetric}", this); controller.registerHandler(GET, "/{index}/_stats", this); controller.registerHandler(GET, "/{index}/_stats/{metric}", this); } + static Map> METRICS; + + static { + final Map> metrics = new HashMap<>(); + metrics.put("docs", r -> r.docs(true)); + metrics.put("store", r -> r.store(true)); + metrics.put("indexing", r -> r.indexing(true)); + metrics.put("search", r -> r.search(true)); + metrics.put("suggest", r -> r.search(true)); + metrics.put("get", r -> r.get(true)); + metrics.put("merge", r -> r.merge(true)); + metrics.put("refresh", r -> r.refresh(true)); + metrics.put("flush", r -> r.flush(true)); + metrics.put("warmer", r -> r.warmer(true)); + metrics.put("query_cache", r -> r.queryCache(true)); + metrics.put("segments", r -> r.segments(true)); + metrics.put("fielddata", r -> r.fieldData(true)); + metrics.put("completion", r -> r.completion(true)); + metrics.put("request_cache", r -> r.requestCache(true)); + metrics.put("recovery", r -> r.recovery(true)); + metrics.put("translog", r -> r.translog(true)); + METRICS = Collections.unmodifiableMap(metrics); + } + @Override public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { IndicesStatsRequest indicesStatsRequest = new IndicesStatsRequest(); @@ -65,24 +94,28 @@ public class RestIndicesStatsAction extends BaseRestHandler { // short cut, if no metrics have been specified in URI if (metrics.size() == 1 && metrics.contains("_all")) { indicesStatsRequest.all(); + } else if (metrics.contains("_all")) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, + "request [%s] contains _all and individual metrics [%s]", + request.path(), + request.param("metric"))); } else { indicesStatsRequest.clear(); - indicesStatsRequest.docs(metrics.contains("docs")); - indicesStatsRequest.store(metrics.contains("store")); - indicesStatsRequest.indexing(metrics.contains("indexing")); - indicesStatsRequest.search(metrics.contains("search") || metrics.contains("suggest")); - indicesStatsRequest.get(metrics.contains("get")); - indicesStatsRequest.merge(metrics.contains("merge")); - indicesStatsRequest.refresh(metrics.contains("refresh")); - indicesStatsRequest.flush(metrics.contains("flush")); - indicesStatsRequest.warmer(metrics.contains("warmer")); - indicesStatsRequest.queryCache(metrics.contains("query_cache")); - indicesStatsRequest.segments(metrics.contains("segments")); - indicesStatsRequest.fieldData(metrics.contains("fielddata")); - indicesStatsRequest.completion(metrics.contains("completion")); - indicesStatsRequest.requestCache(metrics.contains("request_cache")); - indicesStatsRequest.recovery(metrics.contains("recovery")); - indicesStatsRequest.translog(metrics.contains("translog")); + // use a sorted set so the unrecognized parameters appear in a reliable sorted order + final Set invalidMetrics = new TreeSet<>(); + for (final String metric : metrics) { + final Consumer consumer = METRICS.get(metric); + if (consumer != null) { + consumer.accept(indicesStatsRequest); + } else { + invalidMetrics.add(metric); + } + } + + if (!invalidMetrics.isEmpty()) { + throw new IllegalArgumentException(unrecognized(request, invalidMetrics, METRICS.keySet(), "metric")); + } } if (request.hasParam("groups")) { diff --git a/core/src/test/java/org/elasticsearch/rest/action/admin/cluster/RestNodesStatsActionTests.java b/core/src/test/java/org/elasticsearch/rest/action/admin/cluster/RestNodesStatsActionTests.java new file mode 100644 index 00000000000..c3764ff8959 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/rest/action/admin/cluster/RestNodesStatsActionTests.java @@ -0,0 +1,144 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.rest.action.admin.cluster; + +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.rest.FakeRestRequest; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.object.HasToString.hasToString; +import static org.mockito.Mockito.mock; + +public class RestNodesStatsActionTests extends ESTestCase { + + private RestNodesStatsAction action; + + @Override + public void setUp() throws Exception { + super.setUp(); + action = new RestNodesStatsAction(Settings.EMPTY, new RestController(Settings.EMPTY, Collections.emptySet())); + } + + public void testUnrecognizedMetric() throws IOException { + final HashMap params = new HashMap<>(); + final String metric = randomAsciiOfLength(64); + params.put("metric", metric); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_nodes/stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat(e, hasToString(containsString("request [/_nodes/stats] contains unrecognized metric: [" + metric + "]"))); + } + + public void testUnrecognizedMetricDidYouMean() throws IOException { + final HashMap params = new HashMap<>(); + params.put("metric", "os,transprot,unrecognized"); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_nodes/stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat( + e, + hasToString( + containsString( + "request [/_nodes/stats] contains unrecognized metrics: [transprot] -> did you mean [transport]?, [unrecognized]"))); + } + + public void testAllRequestWithOtherMetrics() throws IOException { + final HashMap params = new HashMap<>(); + final String metric = randomSubsetOf(1, RestNodesStatsAction.METRICS.keySet()).get(0); + params.put("metric", "_all," + metric); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_nodes/stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat(e, hasToString(containsString("request [/_nodes/stats] contains _all and individual metrics [_all," + metric + "]"))); + } + + public void testUnrecognizedIndexMetric() { + final HashMap params = new HashMap<>(); + params.put("metric", "indices"); + final String indexMetric = randomAsciiOfLength(64); + params.put("index_metric", indexMetric); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_nodes/stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat(e, hasToString(containsString("request [/_nodes/stats] contains unrecognized index metric: [" + indexMetric + "]"))); + } + + public void testUnrecognizedIndexMetricDidYouMean() { + final HashMap params = new HashMap<>(); + params.put("metric", "indices"); + params.put("index_metric", "indexing,stroe,unrecognized"); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_nodes/stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat( + e, + hasToString( + containsString( + "request [/_nodes/stats] contains unrecognized index metrics: [stroe] -> did you mean [store]?, [unrecognized]"))); + } + + public void testIndexMetricsRequestWithoutIndicesMetric() throws IOException { + final HashMap params = new HashMap<>(); + final Set metrics = new HashSet<>(RestNodesStatsAction.METRICS.keySet()); + metrics.remove("indices"); + params.put("metric", randomSubsetOf(1, metrics).get(0)); + final String indexMetric = randomSubsetOf(1, RestNodesStatsAction.FLAGS.keySet()).get(0); + params.put("index_metric", indexMetric); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_nodes/stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat( + e, + hasToString( + containsString("request [/_nodes/stats] contains index metrics [" + indexMetric + "] but indices stats not requested"))); + } + + public void testIndexMetricsRequestOnAllRequest() throws IOException { + final HashMap params = new HashMap<>(); + params.put("metric", "_all"); + final String indexMetric = randomSubsetOf(1, RestNodesStatsAction.FLAGS.keySet()).get(0); + params.put("index_metric", indexMetric); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_nodes/stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat( + e, + hasToString( + containsString("request [/_nodes/stats] contains index metrics [" + indexMetric + "] but all stats requested"))); + } + +} diff --git a/core/src/test/java/org/elasticsearch/rest/action/admin/indices/RestIndicesStatsActionTests.java b/core/src/test/java/org/elasticsearch/rest/action/admin/indices/RestIndicesStatsActionTests.java new file mode 100644 index 00000000000..13ef39ef4df --- /dev/null +++ b/core/src/test/java/org/elasticsearch/rest/action/admin/indices/RestIndicesStatsActionTests.java @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.rest.action.admin.indices; + +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.rest.FakeRestRequest; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.object.HasToString.hasToString; +import static org.mockito.Mockito.mock; + +public class RestIndicesStatsActionTests extends ESTestCase { + + private RestIndicesStatsAction action; + + @Override + public void setUp() throws Exception { + super.setUp(); + action = new RestIndicesStatsAction(Settings.EMPTY, new RestController(Settings.EMPTY, Collections.emptySet())); + } + + public void testUnrecognizedMetric() throws IOException { + final HashMap params = new HashMap<>(); + final String metric = randomAsciiOfLength(64); + params.put("metric", metric); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat(e, hasToString(containsString("request [/_stats] contains unrecognized metric: [" + metric + "]"))); + } + + public void testUnrecognizedMetricDidYouMean() throws IOException { + final HashMap params = new HashMap<>(); + params.put("metric", "request_cache,fieldata,unrecognized"); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat( + e, + hasToString( + containsString( + "request [/_stats] contains unrecognized metrics: [fieldata] -> did you mean [fielddata]?, [unrecognized]"))); + } + + public void testAllRequestWithOtherMetrics() throws IOException { + final HashMap params = new HashMap<>(); + final String metric = randomSubsetOf(1, RestIndicesStatsAction.METRICS.keySet()).get(0); + params.put("metric", "_all," + metric); + final RestRequest request = new FakeRestRequest.Builder().withPath("/_stats").withParams(params).build(); + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> action.prepareRequest(request, mock(NodeClient.class))); + assertThat(e, hasToString(containsString("request [/_stats] contains _all and individual metrics [_all," + metric + "]"))); + } + +} diff --git a/docs/reference/cluster/nodes-stats.asciidoc b/docs/reference/cluster/nodes-stats.asciidoc index 215cf8b8d7a..493d5db5613 100644 --- a/docs/reference/cluster/nodes-stats.asciidoc +++ b/docs/reference/cluster/nodes-stats.asciidoc @@ -65,12 +65,11 @@ of `indices`, `os`, `process`, `jvm`, `transport`, `http`, [source,js] -------------------------------------------------- -# return indices and os -curl -XGET 'http://localhost:9200/_nodes/stats/os' +# return just indices +curl -XGET 'http://localhost:9200/_nodes/stats/indices' # return just os and process curl -XGET 'http://localhost:9200/_nodes/stats/os,process' -# specific type endpoint -curl -XGET 'http://localhost:9200/_nodes/stats/process' +# return just process for node with IP address 10.0.0.1 curl -XGET 'http://localhost:9200/_nodes/10.0.0.1/stats/process' -------------------------------------------------- @@ -280,27 +279,45 @@ the current running process: `process.mem.total_virtual_in_bytes`:: Size in bytes of virtual memory that is guaranteed to be available to the running process - [float] -[[field-data]] -=== Field data statistics +[[indices-stats]] +=== Indices statistics -You can get information about field data memory usage on node -level or on index level. +You can get information about indices stats on node level or on index level. [source,js] -------------------------------------------------- -# Node Stats -curl -XGET 'http://localhost:9200/_nodes/stats/indices/?fields=field1,field2&pretty' +# Node level +curl -XGET 'http://localhost:9200/_nodes/stats/indices/fielddata?fields=field1,field2&pretty' -# Indices Stat +# Index level curl -XGET 'http://localhost:9200/_stats/fielddata/?fields=field1,field2&pretty' # You can use wildcards for field names +curl -XGET 'http://localhost:9200/_nodes/stats/indices/fielddata?fields=field*&pretty' curl -XGET 'http://localhost:9200/_stats/fielddata/?fields=field*&pretty' -curl -XGET 'http://localhost:9200/_nodes/stats/indices/?fields=field*&pretty' -------------------------------------------------- +Supported metrics are: + +* `completion` +* `docs` +* `fielddata` +* `flush` +* `get` +* `indexing` +* `merge` +* `query_cache` +* `recovery` +* `refresh` +* `request_cache` +* `search` +* `segments` +* `store` +* `suggest` +* `translog` +* `warmer` + [float] [[search-groups]] === Search groups diff --git a/docs/reference/indices/flush.asciidoc b/docs/reference/indices/flush.asciidoc index 5864c16d4c1..2b9f519e17c 100644 --- a/docs/reference/indices/flush.asciidoc +++ b/docs/reference/indices/flush.asciidoc @@ -74,7 +74,7 @@ the <> API: [source,sh] -------------------------------------------------- -GET twitter/_stats/commit?level=shards +GET twitter/_stats?level=shards -------------------------------------------------- // CONSOLE // TEST[s/^/PUT twitter\n/] diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/10_index.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/10_index.yaml index 0710b63e5bb..a48907b0205 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/10_index.yaml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/10_index.yaml @@ -100,3 +100,17 @@ setup: - is_false: indices.test1 - is_true: indices.test2 +--- +"Indices stats unrecognized parameter": + - skip: + version: " - 5.99.99" + reason: awaits strict stats handling to be backported to 5.x + - do: + indices.stats: + metric: [ fieldata ] + ignore: 400 + + - match: { status: 400 } + - match: { error.type: illegal_argument_exception } + - match: { error.reason: "request [/_stats/fieldata] contains unrecognized metric: [fieldata] -> did you mean [fielddata]?" } + diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.stats/10_basic.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.stats/10_basic.yaml index 1cd9fef0258..1ec7ddabf98 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.stats/10_basic.yaml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.stats/10_basic.yaml @@ -20,3 +20,17 @@ level: "indices" - is_true: nodes.$master.indices.indices + +--- +"Nodes stats unrecognized parameter": + - skip: + version: " - 5.99.99" + reason: awaits strict stats handling to be backported to 5.x + - do: + nodes.stats: + metric: [ transprot ] + ignore: 400 + + - match: { status: 400 } + - match: { error.type: illegal_argument_exception } + - match: { error.reason: "request [/_nodes/stats/transprot] contains unrecognized metric: [transprot] -> did you mean [transport]?" }