diff --git a/server/src/internalClusterTest/java/org/opensearch/action/admin/cluster/stats/ClusterStatsIT.java b/server/src/internalClusterTest/java/org/opensearch/action/admin/cluster/stats/ClusterStatsIT.java index f23cdbb50b37a..c599423b2606f 100644 --- a/server/src/internalClusterTest/java/org/opensearch/action/admin/cluster/stats/ClusterStatsIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/action/admin/cluster/stats/ClusterStatsIT.java @@ -37,6 +37,7 @@ import org.opensearch.action.admin.cluster.node.stats.NodeStats; import org.opensearch.action.admin.cluster.node.stats.NodesStatsRequest; import org.opensearch.action.admin.cluster.node.stats.NodesStatsResponse; +import org.opensearch.action.admin.cluster.stats.ClusterStatsRequest.SubMetrics; import org.opensearch.client.Client; import org.opensearch.client.Requests; import org.opensearch.cluster.health.ClusterHealthStatus; @@ -505,6 +506,112 @@ public void testNodeRolesWithDataNodeLegacySettings() throws ExecutionException, assertEquals(expectedNodesRoles, Set.of(getNodeRoles(client, 0), getNodeRoles(client, 1))); } + public void testClusterStatsMetricFiltering() { + internalCluster().startNode(); + ensureGreen(); + + client().admin().indices().prepareCreate("test1").setMapping("{\"properties\":{\"foo\":{\"type\": \"keyword\"}}}").get(); + + ClusterStatsResponse response = client().admin() + .cluster() + .prepareClusterStats() + .useAggregatedNodeLevelResponses(randomBoolean()) + .get(); + assertNotNull(response.getIndicesStats()); + assertNotNull(response.getNodesStats()); + assertNotNull(response.getIndicesStats().getMappings()); + assertNotNull(response.getIndicesStats().getAnalysis()); + assertNotNull(response.getNodesStats().getJvm()); + assertNotNull(response.getNodesStats().getOs()); + + response = client().admin() + .cluster() + .prepareClusterStats() + .useAggregatedNodeLevelResponses(randomBoolean()) + .requestMetrics(SubMetrics.allSubMetrics(ClusterStatsRequest.Metric.INDICES)) + .get(); + assertNotNull(response.getIndicesStats()); + assertNotNull(response.getIndicesStats().getShards()); + assertNotNull(response.getIndicesStats().getDocs()); + assertNotNull(response.getIndicesStats().getMappings()); + assertNotNull(response.getIndicesStats().getAnalysis()); + assertNull(response.getNodesStats()); + + response = client().admin() + .cluster() + .prepareClusterStats() + .useAggregatedNodeLevelResponses(randomBoolean()) + .requestMetrics(SubMetrics.allSubMetrics(ClusterStatsRequest.Metric.NODES)) + .get(); + assertNotNull(response.getNodesStats()); + assertNotNull(response.getNodesStats().getJvm()); + assertNotNull(response.getNodesStats().getOs()); + assertNotNull(response.getNodesStats().getPlugins()); + assertNotNull(response.getNodesStats().getFs()); + assertNull(response.getIndicesStats()); + + response = client().admin() + .cluster() + .prepareClusterStats() + .useAggregatedNodeLevelResponses(randomBoolean()) + .requestMetrics(Set.of(SubMetrics.MAPPINGS.metricName(), SubMetrics.ANALYSIS.metricName())) + .get(); + assertNotNull(response.getIndicesStats()); + assertNotNull(response.getIndicesStats().getMappings()); + assertNotNull(response.getIndicesStats().getAnalysis()); + assertNull(response.getIndicesStats().getShards()); + assertNull(response.getIndicesStats().getDocs()); + assertNull(response.getNodesStats()); + + response = client().admin() + .cluster() + .prepareClusterStats() + .useAggregatedNodeLevelResponses(randomBoolean()) + .requestMetrics(Set.of(SubMetrics.OS.metricName(), SubMetrics.PROCESS.metricName())) + .get(); + assertNotNull(response.getNodesStats()); + assertNotNull(response.getNodesStats().getOs()); + assertNotNull(response.getNodesStats().getProcess()); + assertNull(response.getNodesStats().getPlugins()); + assertNull(response.getNodesStats().getFs()); + assertNull(response.getIndicesStats()); + + response = client().admin() + .cluster() + .prepareClusterStats() + .useAggregatedNodeLevelResponses(randomBoolean()) + .requestMetrics( + Set.of( + SubMetrics.OS.metricName(), + SubMetrics.PROCESS.metricName(), + SubMetrics.MAPPINGS.metricName(), + SubMetrics.ANALYSIS.metricName() + ) + ) + .get(); + assertNotNull(response.getIndicesStats()); + assertNotNull(response.getIndicesStats().getMappings()); + assertNotNull(response.getIndicesStats().getAnalysis()); + assertNotNull(response.getNodesStats()); + assertNotNull(response.getNodesStats().getOs()); + assertNotNull(response.getNodesStats().getProcess()); + assertNull(response.getNodesStats().getPlugins()); + assertNull(response.getNodesStats().getFs()); + assertNull(response.getIndicesStats().getShards()); + assertNull(response.getIndicesStats().getDocs()); + + assertThrows( + IllegalStateException.class, + () -> client().admin() + .cluster() + .prepareClusterStats() + .useAggregatedNodeLevelResponses(randomBoolean()) + .requestMetrics(Set.of("random_metric")) + .get() + ); + + } + private Map getExpectedCounts( int dataRoleCount, int masterRoleCount, diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsIndices.java b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsIndices.java index 03a73f45ffe81..ae3bbd68e9117 100644 --- a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsIndices.java +++ b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsIndices.java @@ -32,6 +32,8 @@ package org.opensearch.action.admin.cluster.stats; +import org.opensearch.action.admin.cluster.stats.ClusterStatsRequest.Metric; +import org.opensearch.action.admin.cluster.stats.ClusterStatsRequest.SubMetrics; import org.opensearch.action.admin.indices.stats.CommonStats; import org.opensearch.common.annotation.PublicApi; import org.opensearch.core.xcontent.ToXContentFragment; @@ -47,6 +49,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; /** * Cluster Stats per index @@ -68,14 +72,57 @@ public class ClusterStatsIndices implements ToXContentFragment { private MappingStats mappings; public ClusterStatsIndices(List nodeResponses, MappingStats mappingStats, AnalysisStats analysisStats) { + this(SubMetrics.allSubMetrics(Metric.INDICES), nodeResponses, mappingStats, analysisStats); + + } + + public ClusterStatsIndices( + Set requestedMetrics, + List nodeResponses, + MappingStats mappingStats, + AnalysisStats analysisStats + ) { Map countsPerIndex = new HashMap<>(); - this.docs = new DocsStats(); - this.store = new StoreStats(); - this.fieldData = new FieldDataStats(); - this.queryCache = new QueryCacheStats(); - this.completion = new CompletionStats(); - this.segments = new SegmentsStats(); + Consumer docsStatsConsumer = (docs) -> { + if (SubMetrics.DOCS.containedIn(requestedMetrics)) { + if (this.docs == null) this.docs = new DocsStats(); + this.docs.add(docs); + } + }; + Consumer storeStatsConsumer = (store) -> { + if (SubMetrics.STORE.containedIn(requestedMetrics)) { + if (this.store == null) this.store = new StoreStats(); + this.store.add(store); + } + }; + Consumer fieldDataConsumer = (fieldDataStats) -> { + if (SubMetrics.FIELDDATA.containedIn(requestedMetrics)) { + if (this.fieldData == null) this.fieldData = new FieldDataStats(); + this.fieldData.add(fieldDataStats); + } + }; + + Consumer queryCacheStatsConsumer = (queryCacheStats) -> { + if (SubMetrics.QUERY_CACHE.containedIn(requestedMetrics)) { + if (this.queryCache == null) this.queryCache = new QueryCacheStats(); + this.queryCache.add(queryCacheStats); + } + }; + + Consumer completionStatsConsumer = (completionStats) -> { + if (SubMetrics.COMPLETION.containedIn(requestedMetrics)) { + if (this.completion == null) this.completion = new CompletionStats(); + this.completion.add(completionStats); + } + }; + + Consumer segmentsStatsConsumer = (segmentsStats) -> { + if (SubMetrics.SEGMENTS.containedIn(requestedMetrics)) { + if (this.segments == null) this.segments = new SegmentsStats(); + this.segments.add(segmentsStats); + } + }; for (ClusterStatsNodeResponse r : nodeResponses) { // Aggregated response from the node @@ -92,12 +139,12 @@ public ClusterStatsIndices(List nodeResponses, Mapping } } - docs.add(r.getAggregatedNodeLevelStats().commonStats.docs); - store.add(r.getAggregatedNodeLevelStats().commonStats.store); - fieldData.add(r.getAggregatedNodeLevelStats().commonStats.fieldData); - queryCache.add(r.getAggregatedNodeLevelStats().commonStats.queryCache); - completion.add(r.getAggregatedNodeLevelStats().commonStats.completion); - segments.add(r.getAggregatedNodeLevelStats().commonStats.segments); + docsStatsConsumer.accept(r.getAggregatedNodeLevelStats().commonStats.docs); + storeStatsConsumer.accept(r.getAggregatedNodeLevelStats().commonStats.store); + fieldDataConsumer.accept(r.getAggregatedNodeLevelStats().commonStats.fieldData); + queryCacheStatsConsumer.accept(r.getAggregatedNodeLevelStats().commonStats.queryCache); + completionStatsConsumer.accept(r.getAggregatedNodeLevelStats().commonStats.completion); + segmentsStatsConsumer.accept(r.getAggregatedNodeLevelStats().commonStats.segments); } else { // Default response from the node for (org.opensearch.action.admin.indices.stats.ShardStats shardStats : r.shardsStats()) { @@ -113,21 +160,23 @@ public ClusterStatsIndices(List nodeResponses, Mapping if (shardStats.getShardRouting().primary()) { indexShardStats.primaries++; - docs.add(shardCommonStats.docs); + docsStatsConsumer.accept(shardCommonStats.docs); } - store.add(shardCommonStats.store); - fieldData.add(shardCommonStats.fieldData); - queryCache.add(shardCommonStats.queryCache); - completion.add(shardCommonStats.completion); - segments.add(shardCommonStats.segments); + storeStatsConsumer.accept(shardCommonStats.store); + fieldDataConsumer.accept(shardCommonStats.fieldData); + queryCacheStatsConsumer.accept(shardCommonStats.queryCache); + completionStatsConsumer.accept(shardCommonStats.completion); + segmentsStatsConsumer.accept(shardCommonStats.segments); } } } - shards = new ShardStats(); indexCount = countsPerIndex.size(); - for (final ShardStats indexCountsCursor : countsPerIndex.values()) { - shards.addIndexShardCount(indexCountsCursor); + if (SubMetrics.SHARDS.containedIn(requestedMetrics)) { + shards = new ShardStats(); + for (final ShardStats indexCountsCursor : countsPerIndex.values()) { + shards.addIndexShardCount(indexCountsCursor); + } } this.mappings = mappingStats; @@ -186,13 +235,27 @@ static final class Fields { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.field(Fields.COUNT, indexCount); - shards.toXContent(builder, params); - docs.toXContent(builder, params); - store.toXContent(builder, params); - fieldData.toXContent(builder, params); - queryCache.toXContent(builder, params); - completion.toXContent(builder, params); - segments.toXContent(builder, params); + if (shards != null) { + shards.toXContent(builder, params); + } + if (docs != null) { + docs.toXContent(builder, params); + } + if (store != null) { + store.toXContent(builder, params); + } + if (fieldData != null) { + fieldData.toXContent(builder, params); + } + if (queryCache != null) { + queryCache.toXContent(builder, params); + } + if (completion != null) { + completion.toXContent(builder, params); + } + if (segments != null) { + segments.toXContent(builder, params); + } if (mappings != null) { mappings.toXContent(builder, params); } diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodes.java b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodes.java index b44e9cfc5c74a..a313f2448f4a9 100644 --- a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodes.java +++ b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodes.java @@ -36,6 +36,8 @@ import org.opensearch.action.admin.cluster.node.info.NodeInfo; import org.opensearch.action.admin.cluster.node.info.PluginsAndModules; import org.opensearch.action.admin.cluster.node.stats.NodeStats; +import org.opensearch.action.admin.cluster.stats.ClusterStatsRequest.Metric; +import org.opensearch.action.admin.cluster.stats.ClusterStatsRequest.SubMetrics; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodeRole; import org.opensearch.common.annotation.PublicApi; @@ -90,37 +92,47 @@ public class ClusterStatsNodes implements ToXContentFragment { private final IngestStats ingestStats; ClusterStatsNodes(List nodeResponses) { + this(SubMetrics.allSubMetrics(Metric.NODES), nodeResponses); + } + + ClusterStatsNodes(Set requestedMetrics, List nodeResponses) { this.versions = new HashSet<>(); - this.fs = new FsInfo.Path(); - this.plugins = new HashSet<>(); + boolean isFSInfoRequested = SubMetrics.FS.containedIn(requestedMetrics); + boolean isPluginsInfoRequested = SubMetrics.PLUGINS.containedIn(requestedMetrics); + this.fs = isFSInfoRequested ? new FsInfo.Path() : null; + this.plugins = isPluginsInfoRequested ? new HashSet<>() : null; Set seenAddresses = new HashSet<>(nodeResponses.size()); List nodeInfos = new ArrayList<>(nodeResponses.size()); - List nodeStats = new ArrayList<>(nodeResponses.size()); + List nodesStats = new ArrayList<>(nodeResponses.size()); for (ClusterStatsNodeResponse nodeResponse : nodeResponses) { - nodeInfos.add(nodeResponse.nodeInfo()); - nodeStats.add(nodeResponse.nodeStats()); - this.versions.add(nodeResponse.nodeInfo().getVersion()); - this.plugins.addAll(nodeResponse.nodeInfo().getInfo(PluginsAndModules.class).getPluginInfos()); + NodeInfo nodeInfo = nodeResponse.nodeInfo(); + NodeStats nodeStats = nodeResponse.nodeStats(); + nodeInfos.add(nodeInfo); + nodesStats.add(nodeStats); + this.versions.add(nodeInfo.getVersion()); + if (isPluginsInfoRequested) { + this.plugins.addAll(nodeInfo.getInfo(PluginsAndModules.class).getPluginInfos()); + } // now do the stats that should be deduped by hardware (implemented by ip deduping) - TransportAddress publishAddress = nodeResponse.nodeInfo().getInfo(TransportInfo.class).address().publishAddress(); + TransportAddress publishAddress = nodeInfo.getInfo(TransportInfo.class).address().publishAddress(); final InetAddress inetAddress = publishAddress.address().getAddress(); if (!seenAddresses.add(inetAddress)) { continue; } - if (nodeResponse.nodeStats().getFs() != null) { - this.fs.add(nodeResponse.nodeStats().getFs().getTotal()); + if (isFSInfoRequested && nodeStats.getFs() != null) { + this.fs.add(nodeStats.getFs().getTotal()); } } this.counts = new Counts(nodeInfos); - this.os = new OsStats(nodeInfos, nodeStats); - this.process = new ProcessStats(nodeStats); - this.jvm = new JvmStats(nodeInfos, nodeStats); - this.networkTypes = new NetworkTypes(nodeInfos); - this.discoveryTypes = new DiscoveryTypes(nodeInfos); - this.packagingTypes = new PackagingTypes(nodeInfos); - this.ingestStats = new IngestStats(nodeStats); + this.os = SubMetrics.OS.containedIn(requestedMetrics) ? new OsStats(nodeInfos, nodesStats) : null; + this.process = SubMetrics.PROCESS.containedIn(requestedMetrics) ? new ProcessStats(nodesStats) : null; + this.jvm = SubMetrics.JVM.containedIn(requestedMetrics) ? new JvmStats(nodeInfos, nodesStats) : null; + this.networkTypes = SubMetrics.NETWORK_TYPES.containedIn(requestedMetrics) ? new NetworkTypes(nodeInfos) : null; + this.discoveryTypes = SubMetrics.DISCOVERY_TYPES.containedIn(requestedMetrics) ? new DiscoveryTypes(nodeInfos) : null; + this.packagingTypes = SubMetrics.PACKAGING_TYPES.containedIn(requestedMetrics) ? new PackagingTypes(nodeInfos) : null; + this.ingestStats = SubMetrics.INGEST.containedIn(requestedMetrics) ? new IngestStats(nodesStats) : null; } public Counts getCounts() { @@ -179,36 +191,54 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } builder.endArray(); - builder.startObject(Fields.OS); - os.toXContent(builder, params); - builder.endObject(); + if (os != null) { + builder.startObject(Fields.OS); + os.toXContent(builder, params); + builder.endObject(); + } - builder.startObject(Fields.PROCESS); - process.toXContent(builder, params); - builder.endObject(); + if (process != null) { + builder.startObject(Fields.PROCESS); + process.toXContent(builder, params); + builder.endObject(); + } - builder.startObject(Fields.JVM); - jvm.toXContent(builder, params); - builder.endObject(); + if (jvm != null) { + builder.startObject(Fields.JVM); + jvm.toXContent(builder, params); + builder.endObject(); + } - builder.field(Fields.FS); - fs.toXContent(builder, params); + if (fs != null) { + builder.field(Fields.FS); + fs.toXContent(builder, params); + } - builder.startArray(Fields.PLUGINS); - for (PluginInfo pluginInfo : plugins) { - pluginInfo.toXContent(builder, params); + if (plugins != null) { + builder.startArray(Fields.PLUGINS); + for (PluginInfo pluginInfo : plugins) { + pluginInfo.toXContent(builder, params); + } + builder.endArray(); } - builder.endArray(); - builder.startObject(Fields.NETWORK_TYPES); - networkTypes.toXContent(builder, params); - builder.endObject(); + if (networkTypes != null) { + builder.startObject(Fields.NETWORK_TYPES); + networkTypes.toXContent(builder, params); + builder.endObject(); + } - discoveryTypes.toXContent(builder, params); + if (discoveryTypes != null) { + discoveryTypes.toXContent(builder, params); + } - packagingTypes.toXContent(builder, params); + if (packagingTypes != null) { + packagingTypes.toXContent(builder, params); + } - ingestStats.toXContent(builder, params); + if (ingestStats != null) { + ingestStats.toXContent(builder, params); + } return builder; } diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequest.java b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequest.java index b82a9d256a134..1ebbca3fd52b0 100644 --- a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequest.java +++ b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequest.java @@ -39,6 +39,10 @@ import org.opensearch.core.common.io.stream.StreamOutput; import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; /** * A request to get cluster level stats. @@ -48,11 +52,17 @@ @PublicApi(since = "1.0.0") public class ClusterStatsRequest extends BaseNodesRequest { + private final Set requestedMetrics = new HashSet<>(SubMetrics.allSubMetrics()); + public ClusterStatsRequest(StreamInput in) throws IOException { super(in); if (in.getVersion().onOrAfter(Version.V_2_16_0)) { useAggregatedNodeLevelResponses = in.readOptionalBoolean(); } + if (in.getVersion().onOrAfter(Version.V_3_0_0)) { + requestedMetrics.clear(); + requestedMetrics.addAll(in.readStringList()); + } } private Boolean useAggregatedNodeLevelResponses = false; @@ -73,12 +83,124 @@ public void useAggregatedNodeLevelResponses(boolean useAggregatedNodeLevelRespon this.useAggregatedNodeLevelResponses = useAggregatedNodeLevelResponses; } + /** + * Get the names of requested metrics, excluding indices, which are + * handled separately. + */ + public Set requestedMetrics() { + return new HashSet<>(requestedMetrics); + } + + /** + * Add subMetric + */ + public ClusterStatsRequest addMetric(String subMetric) { + if (SubMetrics.allSubMetrics().contains(subMetric) == false) { + throw new IllegalStateException("Used an illegal subMetric: " + subMetric); + } + requestedMetrics.add(subMetric); + return this; + } + + public void clearMetrics() { + requestedMetrics.clear(); + } + @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); if (out.getVersion().onOrAfter(Version.V_2_16_0)) { out.writeOptionalBoolean(useAggregatedNodeLevelResponses); } + if (out.getVersion().onOrAfter(Version.V_3_0_0)) { + out.writeStringArray(requestedMetrics.toArray(new String[0])); + } + } + + /** + * An enumeration of the "core" sections of metrics that may be requested + * from the cluster stats endpoint. + */ + @PublicApi(since = "3.0.0") + public enum Metric { + INDICES("indices"), + NODES("nodes"); + + private String metricName; + + Metric(String name) { + this.metricName = name; + } + + public String metricName() { + return this.metricName; + } + + public Set getSubMetrics() { + return SubMetrics.allSubMetrics(this); + } } + /** + * An enumeration of the "core" sections of sub metrics that may be requested + * from the cluster stats endpoint. + */ + public enum SubMetrics { + SHARDS("shards", Metric.INDICES), + DOCS("docs", Metric.INDICES), + STORE("store", Metric.INDICES), + FIELDDATA("fielddata", Metric.INDICES), + QUERY_CACHE("query_cache", Metric.INDICES), + COMPLETION("completion", Metric.INDICES), + SEGMENTS("segments", Metric.INDICES), + ANALYSIS("analysis", Metric.INDICES), + MAPPINGS("mappings", Metric.INDICES), + + OS("os", Metric.NODES), + PROCESS("process", Metric.NODES), + JVM("jvm", Metric.NODES), + FS("fs", Metric.NODES), + PLUGINS("plugins", Metric.NODES), + INGEST("ingest", Metric.NODES), + NETWORK_TYPES("network_types", Metric.NODES), + DISCOVERY_TYPES("discovery_types", Metric.NODES), + PACKAGING_TYPES("packaging_types", Metric.NODES); + + private String metricName; + + private Metric metricType; + + SubMetrics(String name, Metric type) { + this.metricName = name; + this.metricType = type; + } + + public String metricName() { + return this.metricName; + } + + public Metric metricType() { + return this.metricType; + } + + public boolean containedIn(Set metricNames) { + return metricNames.contains(this.metricName()); + } + + public static Set allSubMetrics() { + return Arrays.stream(values()).map(SubMetrics::metricName).collect(Collectors.toSet()); + } + + public static Set allSubMetrics(Metric metricType) { + return Arrays.stream(values()) + .filter(metric -> metricType.equals(metric.metricType())) + .map(SubMetrics::metricName) + .collect(Collectors.toSet()); + } + + public static boolean containsMetricType(Set metricNames, Metric metricType) { + return allSubMetrics(metricType).stream().anyMatch(metricNames::contains); + } + + } } diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequestBuilder.java b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequestBuilder.java index 4d0932bd3927d..6655690b01d85 100644 --- a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequestBuilder.java +++ b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequestBuilder.java @@ -36,6 +36,8 @@ import org.opensearch.client.OpenSearchClient; import org.opensearch.common.annotation.PublicApi; +import java.util.Set; + /** * Transport request builder for obtaining cluster stats * @@ -55,4 +57,10 @@ public final ClusterStatsRequestBuilder useAggregatedNodeLevelResponses(boolean request.useAggregatedNodeLevelResponses(useAggregatedNodeLevelResponses); return this; } + + public final ClusterStatsRequestBuilder requestMetrics(Set requestMetrics) { + request.clearMetrics(); + requestMetrics.forEach(request::addMetric); + return this; + } } diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsResponse.java b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsResponse.java index cc002b689a2a5..bd4d709a0d73e 100644 --- a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsResponse.java +++ b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsResponse.java @@ -33,6 +33,7 @@ package org.opensearch.action.admin.cluster.stats; import org.opensearch.action.FailedNodeException; +import org.opensearch.action.admin.cluster.stats.ClusterStatsRequest.Metric; import org.opensearch.action.support.nodes.BaseNodesResponse; import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.ClusterState; @@ -47,6 +48,7 @@ import java.io.IOException; import java.util.List; import java.util.Locale; +import java.util.Set; /** * Transport response for obtaining cluster stats @@ -88,12 +90,32 @@ public ClusterStatsResponse( List nodes, List failures, ClusterState state + ) { + this(timestamp, clusterUUID, clusterName, nodes, failures, state, ClusterStatsRequest.SubMetrics.allSubMetrics()); + } + + public ClusterStatsResponse( + long timestamp, + String clusterUUID, + ClusterName clusterName, + List nodes, + List failures, + ClusterState state, + Set requestedMetrics ) { super(clusterName, nodes, failures); this.clusterUUID = clusterUUID; this.timestamp = timestamp; - nodesStats = new ClusterStatsNodes(nodes); - indicesStats = new ClusterStatsIndices(nodes, MappingStats.of(state), AnalysisStats.of(state)); + MappingStats mappingStats = ClusterStatsRequest.SubMetrics.MAPPINGS.containedIn(requestedMetrics) ? MappingStats.of(state) : null; + AnalysisStats analysisStats = ClusterStatsRequest.SubMetrics.ANALYSIS.containedIn(requestedMetrics) + ? AnalysisStats.of(state) + : null; + nodesStats = ClusterStatsRequest.SubMetrics.containsMetricType(requestedMetrics, Metric.NODES) + ? new ClusterStatsNodes(requestedMetrics, nodes) + : null; + indicesStats = ClusterStatsRequest.SubMetrics.containsMetricType(requestedMetrics, Metric.INDICES) + ? new ClusterStatsIndices(requestedMetrics, nodes, mappingStats, analysisStats) + : null; ClusterHealthStatus status = null; for (ClusterStatsNodeResponse response : nodes) { // only the cluster-manager node populates the status @@ -131,8 +153,13 @@ public void writeTo(StreamOutput out) throws IOException { out.writeVLong(timestamp); out.writeOptionalWriteable(status); out.writeOptionalString(clusterUUID); - out.writeOptionalWriteable(indicesStats.getMappings()); - out.writeOptionalWriteable(indicesStats.getAnalysis()); + if (indicesStats != null) { + out.writeOptionalWriteable(indicesStats.getMappings()); + out.writeOptionalWriteable(indicesStats.getAnalysis()); + } else { + out.writeOptionalWriteable(null); + out.writeOptionalWriteable(null); + } } @Override @@ -153,12 +180,16 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (status != null) { builder.field("status", status.name().toLowerCase(Locale.ROOT)); } - builder.startObject("indices"); - indicesStats.toXContent(builder, params); - builder.endObject(); - builder.startObject("nodes"); - nodesStats.toXContent(builder, params); - builder.endObject(); + if (indicesStats != null) { + builder.startObject("indices"); + indicesStats.toXContent(builder, params); + builder.endObject(); + } + if (nodesStats != null) { + builder.startObject("nodes"); + nodesStats.toXContent(builder, params); + builder.endObject(); + } return builder; } diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/stats/TransportClusterStatsAction.java b/server/src/main/java/org/opensearch/action/admin/cluster/stats/TransportClusterStatsAction.java index a49ca2035783c..0e344ee15cff8 100644 --- a/server/src/main/java/org/opensearch/action/admin/cluster/stats/TransportClusterStatsAction.java +++ b/server/src/main/java/org/opensearch/action/admin/cluster/stats/TransportClusterStatsAction.java @@ -130,7 +130,8 @@ protected ClusterStatsResponse newResponse( clusterService.getClusterName(), responses, failures, - state + state, + request.requestedMetrics() ); } diff --git a/server/src/main/java/org/opensearch/rest/action/admin/cluster/RestClusterStatsAction.java b/server/src/main/java/org/opensearch/rest/action/admin/cluster/RestClusterStatsAction.java index ee33bd18db05d..f0f1c4ec87488 100644 --- a/server/src/main/java/org/opensearch/rest/action/admin/cluster/RestClusterStatsAction.java +++ b/server/src/main/java/org/opensearch/rest/action/admin/cluster/RestClusterStatsAction.java @@ -33,13 +33,23 @@ package org.opensearch.rest.action.admin.cluster; import org.opensearch.action.admin.cluster.stats.ClusterStatsRequest; +import org.opensearch.action.admin.cluster.stats.ClusterStatsRequest.SubMetrics; import org.opensearch.client.node.NodeClient; +import org.opensearch.core.common.Strings; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestActions.NodesResponseRestListener; import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Consumer; import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableList; @@ -54,7 +64,36 @@ public class RestClusterStatsAction extends BaseRestHandler { @Override public List routes() { - return unmodifiableList(asList(new Route(GET, "/_cluster/stats"), new Route(GET, "/_cluster/stats/nodes/{nodeId}"))); + return unmodifiableList( + asList( + new Route(GET, "/_cluster/stats"), + new Route(GET, "/_cluster/stats/nodes/{nodeId}"), + new Route(GET, "/_cluster/stats/metrics/{metric}"), + new Route(GET, "/_cluster/stats/metrics/{metric}/{sub_metric}"), + new Route(GET, "/_cluster/stats/nodes/{nodeId}/metrics/{metric}"), + new Route(GET, "/_cluster/stats/nodes/{nodeId}/metrics/{metric}/{sub_metric}") + ) + ); + } + + static final Map> METRIC_TO_SUB_METRICS_MAP; + + static { + Map> metricMap = new HashMap<>(); + for (ClusterStatsRequest.Metric metric : ClusterStatsRequest.Metric.values()) { + metricMap.put(metric.metricName(), metric.getSubMetrics()); + } + METRIC_TO_SUB_METRICS_MAP = Collections.unmodifiableMap(metricMap); + } + + static final Map> SUB_METRIC_REQUEST_CONSUMER_MAP; + + static { + Map> subMetricMap = new HashMap<>(); + for (SubMetrics subMetric : SubMetrics.values()) { + subMetricMap.put(subMetric.metricName(), request -> request.addMetric(subMetric.metricName())); + } + SUB_METRIC_REQUEST_CONSUMER_MAP = Collections.unmodifiableMap(subMetricMap); } @Override @@ -64,9 +103,78 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { - ClusterStatsRequest clusterStatsRequest = new ClusterStatsRequest().nodesIds(request.paramAsStringArray("nodeId", null)); + String[] nodeIds = request.paramAsStringArray("nodeId", null); + Set metrics = Strings.tokenizeByCommaToSet(request.param("metric", "_all")); + Set subMetrics = Strings.tokenizeByCommaToSet(request.param("sub_metric", "_all")); + + ClusterStatsRequest clusterStatsRequest = new ClusterStatsRequest().nodesIds(nodeIds); clusterStatsRequest.timeout(request.param("timeout")); clusterStatsRequest.useAggregatedNodeLevelResponses(true); + + if (metrics.size() > 1 && metrics.contains("_all")) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "request [%s] contains _all and individual metrics [%s]", + request.path(), + request.param("metric") + ) + ); + } else if (subMetrics.size() > 1 && subMetrics.contains("_all")) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "request [%s] contains _all and individual sub metrics [%s]", + request.path(), + request.param("sub_metric") + ) + ); + } else { + clusterStatsRequest.clearMetrics(); + final Set eligibleSubMetrics = new HashSet<>(); + final Set invalidMetrics = new TreeSet<>(); + if (metrics.contains("_all")) { + eligibleSubMetrics.addAll(SubMetrics.allSubMetrics()); + } else { + for (String metric : metrics) { + Set metricTypeSubMetrics = METRIC_TO_SUB_METRICS_MAP.get(metric); + if (metricTypeSubMetrics != null) { + eligibleSubMetrics.addAll(metricTypeSubMetrics); + } else { + invalidMetrics.add(metric); + } + } + } + if (!invalidMetrics.isEmpty()) { + throw new IllegalArgumentException(unrecognized(request, invalidMetrics, METRIC_TO_SUB_METRICS_MAP.keySet(), "metric")); + } + + final Set subMetricsRequested = new HashSet<>(); + final Set invalidSubMetrics = new TreeSet<>(); + if (subMetrics.contains("_all")) { + subMetricsRequested.addAll(eligibleSubMetrics); + } else { + for (String subMetric : subMetrics) { + if (eligibleSubMetrics.contains(subMetric)) { + subMetricsRequested.add(subMetric); + } else { + invalidSubMetrics.add(subMetric); + } + } + } + + if (!invalidSubMetrics.isEmpty()) { + throw new IllegalArgumentException( + unrecognized(request, invalidSubMetrics, SUB_METRIC_REQUEST_CONSUMER_MAP.keySet(), "sub_metric") + ); + } + + for (String subMetric : subMetricsRequested) { + SUB_METRIC_REQUEST_CONSUMER_MAP.get(subMetric).accept(clusterStatsRequest); + } + + } + return channel -> client.admin().cluster().clusterStats(clusterStatsRequest, new NodesResponseRestListener<>(channel)); } diff --git a/server/src/test/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequestBuilderTest.java b/server/src/test/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequestBuilderTest.java new file mode 100644 index 0000000000000..d0087845158e7 --- /dev/null +++ b/server/src/test/java/org/opensearch/action/admin/cluster/stats/ClusterStatsRequestBuilderTest.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.action.admin.cluster.stats; + +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.test.client.NoOpClient; +import org.junit.After; +import org.junit.Before; + +public class ClusterStatsRequestBuilderTest extends OpenSearchTestCase { + + private NoOpClient testClient; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + this.testClient = new NoOpClient(getTestName()); + } + + @Override + @After + public void tearDown() throws Exception { + this.testClient.close(); + super.tearDown(); + } + + public void testUseAggregatedNodeLevelResponses() { + ClusterStatsRequestBuilder clusterStatsRequestBuilder = new ClusterStatsRequestBuilder( + this.testClient, + ClusterStatsAction.INSTANCE + ); + clusterStatsRequestBuilder.useAggregatedNodeLevelResponses(false); + assertFalse(clusterStatsRequestBuilder.request().useAggregatedNodeLevelResponses()); + } + + public void testRequestMetrics() { + ClusterStatsRequestBuilder clusterStatsRequestBuilder = new ClusterStatsRequestBuilder( + this.testClient, + ClusterStatsAction.INSTANCE + ); + clusterStatsRequestBuilder.requestMetrics(ClusterStatsRequest.SubMetrics.allSubMetrics(ClusterStatsRequest.Metric.INDICES)); + assertEquals( + ClusterStatsRequest.SubMetrics.allSubMetrics(ClusterStatsRequest.Metric.INDICES), + clusterStatsRequestBuilder.request().requestedMetrics() + ); + } + +}