From e4329b8e51da1613a446756f31096d2e4ecda883 Mon Sep 17 00:00:00 2001 From: Pranshu Shukla Date: Fri, 12 Jul 2024 17:12:48 +0530 Subject: [PATCH] Optimize Cluster Stats Indices to precomute node level stats Signed-off-by: Pranshu Shukla --- .../admin/cluster/stats/ClusterStatsIT.java | 74 ++++-- .../cluster/stats/ClusterStatsIndices.java | 86 ++++-- .../stats/ClusterStatsNodeResponse.java | 43 ++- .../cluster/stats/ClusterStatsRequest.java | 17 ++ .../stats/ClusterStatsRequestBuilder.java | 5 + .../cluster/stats/NodeIndexShardStats.java | 98 +++++++ .../stats/TransportClusterStatsAction.java | 10 +- .../admin/cluster/RestClusterStatsAction.java | 1 + .../cluster/stats/ClusterStatsNodesTests.java | 244 ++++++++++++++++++ 9 files changed, 536 insertions(+), 42 deletions(-) create mode 100644 server/src/main/java/org/opensearch/action/admin/cluster/stats/NodeIndexShardStats.java 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 085a32593063a..c0b1539b3b8d8 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 @@ -88,7 +88,11 @@ public void testNodeCounts() { Map expectedCounts = getExpectedCounts(1, 1, 1, 1, 1, 0, 0); int numNodes = randomIntBetween(1, 5); - ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse response = client().admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertCounts(response.getNodesStats().getCounts(), total, expectedCounts); for (int i = 0; i < numNodes; i++) { @@ -153,7 +157,11 @@ public void testNodeCountsWithDeprecatedMasterRole() throws ExecutionException, Map expectedCounts = getExpectedCounts(0, 1, 1, 0, 0, 0, 0); Client client = client(); - ClusterStatsResponse response = client.admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse response = client.admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertCounts(response.getNodesStats().getCounts(), total, expectedCounts); Set expectedRoles = Set.of(DiscoveryNodeRole.MASTER_ROLE.roleName()); @@ -179,7 +187,11 @@ private void assertShardStats(ClusterStatsIndices.ShardStats stats, int indices, public void testIndicesShardStats() throws ExecutionException, InterruptedException { internalCluster().startNode(); ensureGreen(); - ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse response = client().admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertThat(response.getStatus(), Matchers.equalTo(ClusterHealthStatus.GREEN)); prepareCreate("test1").setSettings(Settings.builder().put("number_of_shards", 2).put("number_of_replicas", 1)).get(); @@ -195,14 +207,14 @@ public void testIndicesShardStats() throws ExecutionException, InterruptedExcept ensureGreen(); index("test1", "type", "1", "f", "f"); refresh(); // make the doc visible - response = client().admin().cluster().prepareClusterStats().get(); + response = client().admin().cluster().prepareClusterStats().useOptimizedClusterStatsResponse(randomBoolean()).get(); assertThat(response.getStatus(), Matchers.equalTo(ClusterHealthStatus.GREEN)); assertThat(response.indicesStats.getDocs().getCount(), Matchers.equalTo(1L)); assertShardStats(response.getIndicesStats().getShards(), 1, 4, 2, 1.0); prepareCreate("test2").setSettings(Settings.builder().put("number_of_shards", 3).put("number_of_replicas", 0)).get(); ensureGreen(); - response = client().admin().cluster().prepareClusterStats().get(); + response = client().admin().cluster().prepareClusterStats().useOptimizedClusterStatsResponse(randomBoolean()).get(); assertThat(response.getStatus(), Matchers.equalTo(ClusterHealthStatus.GREEN)); assertThat(response.indicesStats.getIndexCount(), Matchers.equalTo(2)); assertShardStats(response.getIndicesStats().getShards(), 2, 7, 5, 2.0 / 5); @@ -225,7 +237,11 @@ public void testValuesSmokeScreen() throws IOException, ExecutionException, Inte internalCluster().startNodes(randomIntBetween(1, 3)); index("test1", "type", "1", "f", "f"); - ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse response = client().admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); String msg = response.toString(); assertThat(msg, response.getTimestamp(), Matchers.greaterThan(946681200000L)); // 1 Jan 2000 assertThat(msg, response.indicesStats.getStore().getSizeInBytes(), Matchers.greaterThan(0L)); @@ -265,13 +281,21 @@ public void testAllocatedProcessors() throws Exception { internalCluster().startNode(Settings.builder().put(OpenSearchExecutors.NODE_PROCESSORS_SETTING.getKey(), 7).build()); waitForNodes(1); - ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse response = client().admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertThat(response.getNodesStats().getOs().getAllocatedProcessors(), equalTo(7)); } public void testClusterStatusWhenStateNotRecovered() throws Exception { internalCluster().startClusterManagerOnlyNode(Settings.builder().put("gateway.recover_after_nodes", 2).build()); - ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse response = client().admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertThat(response.getStatus(), equalTo(ClusterHealthStatus.RED)); if (randomBoolean()) { @@ -281,14 +305,18 @@ public void testClusterStatusWhenStateNotRecovered() throws Exception { } // wait for the cluster status to settle ensureGreen(); - response = client().admin().cluster().prepareClusterStats().get(); + response = client().admin().cluster().prepareClusterStats().useOptimizedClusterStatsResponse(randomBoolean()).get(); assertThat(response.getStatus(), equalTo(ClusterHealthStatus.GREEN)); } public void testFieldTypes() { internalCluster().startNode(); ensureGreen(); - ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse response = client().admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertThat(response.getStatus(), Matchers.equalTo(ClusterHealthStatus.GREEN)); assertTrue(response.getIndicesStats().getMappings().getFieldTypeStats().isEmpty()); @@ -301,7 +329,7 @@ public void testFieldTypes() { + "\"eggplant\":{\"type\":\"integer\"}}}}}" ) .get(); - response = client().admin().cluster().prepareClusterStats().get(); + response = client().admin().cluster().prepareClusterStats().useOptimizedClusterStatsResponse(randomBoolean()).get(); assertThat(response.getIndicesStats().getMappings().getFieldTypeStats().size(), equalTo(3)); Set stats = response.getIndicesStats().getMappings().getFieldTypeStats(); for (IndexFeatureStats stat : stats) { @@ -329,7 +357,11 @@ public void testNodeRolesWithMasterLegacySettings() throws ExecutionException, I Map expectedCounts = getExpectedCounts(0, 1, 1, 0, 1, 0, 0); Client client = client(); - ClusterStatsResponse clusterStatsResponse = client.admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse clusterStatsResponse = client.admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertCounts(clusterStatsResponse.getNodesStats().getCounts(), total, expectedCounts); Set expectedRoles = Set.of( @@ -359,7 +391,11 @@ public void testNodeRolesWithClusterManagerRole() throws ExecutionException, Int Map expectedCounts = getExpectedCounts(0, 1, 1, 0, 1, 0, 0); Client client = client(); - ClusterStatsResponse clusterStatsResponse = client.admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse clusterStatsResponse = client.admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertCounts(clusterStatsResponse.getNodesStats().getCounts(), total, expectedCounts); Set expectedRoles = Set.of( @@ -383,7 +419,11 @@ public void testNodeRolesWithSeedDataNodeLegacySettings() throws ExecutionExcept Map expectedRoleCounts = getExpectedCounts(1, 1, 1, 0, 1, 0, 0); Client client = client(); - ClusterStatsResponse clusterStatsResponse = client.admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse clusterStatsResponse = client.admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertCounts(clusterStatsResponse.getNodesStats().getCounts(), total, expectedRoleCounts); Set expectedRoles = Set.of( @@ -410,7 +450,11 @@ public void testNodeRolesWithDataNodeLegacySettings() throws ExecutionException, Map expectedRoleCounts = getExpectedCounts(1, 1, 1, 0, 1, 0, 0); Client client = client(); - ClusterStatsResponse clusterStatsResponse = client.admin().cluster().prepareClusterStats().get(); + ClusterStatsResponse clusterStatsResponse = client.admin() + .cluster() + .prepareClusterStats() + .useOptimizedClusterStatsResponse(randomBoolean()) + .get(); assertCounts(clusterStatsResponse.getNodesStats().getCounts(), total, expectedRoleCounts); Set> expectedNodesRoles = Set.of( 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 26e554f44fca1..06678e335df01 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 @@ -34,6 +34,9 @@ import org.opensearch.action.admin.indices.stats.CommonStats; import org.opensearch.common.annotation.PublicApi; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; import org.opensearch.core.xcontent.ToXContentFragment; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.cache.query.QueryCacheStats; @@ -78,26 +81,44 @@ public ClusterStatsIndices(List nodeResponses, Mapping this.segments = new SegmentsStats(); for (ClusterStatsNodeResponse r : nodeResponses) { - for (org.opensearch.action.admin.indices.stats.ShardStats shardStats : r.shardsStats()) { - ShardStats indexShardStats = countsPerIndex.get(shardStats.getShardRouting().getIndexName()); - if (indexShardStats == null) { - indexShardStats = new ShardStats(); - countsPerIndex.put(shardStats.getShardRouting().getIndexName(), indexShardStats); - } - - indexShardStats.total++; - - CommonStats shardCommonStats = shardStats.getStats(); - - if (shardStats.getShardRouting().primary()) { - indexShardStats.primaries++; - docs.add(shardCommonStats.docs); + // Optimized response from the node + if (r.getNodeIndexShardStats() != null) { + r.getNodeIndexShardStats().indexStatsMap.forEach( + (index, indexCountStats) -> countsPerIndex.merge(index, indexCountStats, (v1, v2) -> { + v1.addStatsFrom(v2); + return v1; + }) + ); + + docs.add(r.getNodeIndexShardStats().docs); + store.add(r.getNodeIndexShardStats().store); + fieldData.add(r.getNodeIndexShardStats().fieldData); + queryCache.add(r.getNodeIndexShardStats().queryCache); + completion.add(r.getNodeIndexShardStats().completion); + segments.add(r.getNodeIndexShardStats().segments); + } else { + // Default response from the node + for (org.opensearch.action.admin.indices.stats.ShardStats shardStats : r.shardsStats()) { + ShardStats indexShardStats = countsPerIndex.get(shardStats.getShardRouting().getIndexName()); + if (indexShardStats == null) { + indexShardStats = new ShardStats(); + countsPerIndex.put(shardStats.getShardRouting().getIndexName(), indexShardStats); + } + + indexShardStats.total++; + + CommonStats shardCommonStats = shardStats.getStats(); + + if (shardStats.getShardRouting().primary()) { + indexShardStats.primaries++; + docs.add(shardCommonStats.docs); + } + store.add(shardCommonStats.store); + fieldData.add(shardCommonStats.fieldData); + queryCache.add(shardCommonStats.queryCache); + completion.add(shardCommonStats.completion); + segments.add(shardCommonStats.segments); } - store.add(shardCommonStats.store); - fieldData.add(shardCommonStats.fieldData); - queryCache.add(shardCommonStats.queryCache); - completion.add(shardCommonStats.completion); - segments.add(shardCommonStats.segments); } } @@ -185,11 +206,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws * @opensearch.api */ @PublicApi(since = "1.0.0") - public static class ShardStats implements ToXContentFragment { + public static class ShardStats implements ToXContentFragment, Writeable { - int indices; - int total; - int primaries; + int indices = 0; + int total = 0; + int primaries = 0; // min/max int minIndexShards = -1; @@ -202,6 +223,12 @@ public static class ShardStats implements ToXContentFragment { public ShardStats() {} + public ShardStats(StreamInput in) throws IOException { + indices = in.readVInt(); + total = in.readVInt(); + primaries = in.readVInt(); + } + /** * number of indices in the cluster */ @@ -329,6 +356,19 @@ public void addIndexShardCount(ShardStats indexShardCount) { } } + public void addStatsFrom(ShardStats incomingStats) { + this.total += incomingStats.getTotal(); + this.indices += incomingStats.getIndices(); + this.primaries += incomingStats.getPrimaries(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(indices); + out.writeVInt(total); + out.writeVInt(primaries); + } + /** * Inner Fields used for creating XContent and parsing * diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodeResponse.java b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodeResponse.java index 1b25bf84356d6..6df24f44432e1 100644 --- a/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodeResponse.java +++ b/server/src/main/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodeResponse.java @@ -32,6 +32,7 @@ package org.opensearch.action.admin.cluster.stats; +import org.opensearch.Version; import org.opensearch.action.admin.cluster.node.info.NodeInfo; import org.opensearch.action.admin.cluster.node.stats.NodeStats; import org.opensearch.action.admin.indices.stats.ShardStats; @@ -55,6 +56,7 @@ public class ClusterStatsNodeResponse extends BaseNodeResponse { private final NodeStats nodeStats; private final ShardStats[] shardsStats; private ClusterHealthStatus clusterStatus; + private NodeIndexShardStats nodeIndexShardStats; public ClusterStatsNodeResponse(StreamInput in) throws IOException { super(in); @@ -64,7 +66,12 @@ public ClusterStatsNodeResponse(StreamInput in) throws IOException { } this.nodeInfo = new NodeInfo(in); this.nodeStats = new NodeStats(in); - shardsStats = in.readArray(ShardStats::new, ShardStats[]::new); + if (in.getVersion().onOrAfter(Version.V_3_0_0)) { + this.shardsStats = in.readOptionalArray(ShardStats::new, ShardStats[]::new); + this.nodeIndexShardStats = in.readOptionalWriteable(NodeIndexShardStats::new); + } else { + this.shardsStats = in.readArray(ShardStats::new, ShardStats[]::new); + } } public ClusterStatsNodeResponse( @@ -81,6 +88,24 @@ public ClusterStatsNodeResponse( this.clusterStatus = clusterStatus; } + public ClusterStatsNodeResponse( + DiscoveryNode node, + @Nullable ClusterHealthStatus clusterStatus, + NodeInfo nodeInfo, + NodeStats nodeStats, + ShardStats[] shardsStats, + boolean optimized + ) { + super(node); + this.nodeInfo = nodeInfo; + this.nodeStats = nodeStats; + if (optimized) { + this.nodeIndexShardStats = new NodeIndexShardStats(node, shardsStats); + } + this.shardsStats = shardsStats; + this.clusterStatus = clusterStatus; + } + public NodeInfo nodeInfo() { return this.nodeInfo; } @@ -101,6 +126,10 @@ public ShardStats[] shardsStats() { return this.shardsStats; } + public NodeIndexShardStats getNodeIndexShardStats() { + return nodeIndexShardStats; + } + public static ClusterStatsNodeResponse readNodeResponse(StreamInput in) throws IOException { return new ClusterStatsNodeResponse(in); } @@ -116,6 +145,16 @@ public void writeTo(StreamOutput out) throws IOException { } nodeInfo.writeTo(out); nodeStats.writeTo(out); - out.writeArray(shardsStats); + if (out.getVersion().onOrAfter(Version.V_3_0_0)) { + if (nodeIndexShardStats != null) { + out.writeOptionalArray(null); + out.writeOptionalWriteable(nodeIndexShardStats); + } else { + out.writeOptionalArray(shardsStats); + out.writeOptionalWriteable(null); + } + } else { + out.writeArray(shardsStats); + } } } 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 6a99451c596ed..f85c964c1eea8 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 @@ -32,6 +32,7 @@ package org.opensearch.action.admin.cluster.stats; +import org.opensearch.Version; import org.opensearch.action.support.nodes.BaseNodesRequest; import org.opensearch.common.annotation.PublicApi; import org.opensearch.core.common.io.stream.StreamInput; @@ -49,8 +50,13 @@ public class ClusterStatsRequest extends BaseNodesRequest { public ClusterStatsRequest(StreamInput in) throws IOException { super(in); + if (in.getVersion().onOrAfter(Version.V_3_0_0)) { + useOptimizedClusterStatsResponse = in.readOptionalBoolean(); + } } + private Boolean useOptimizedClusterStatsResponse = false; + /** * Get stats from nodes based on the nodes ids specified. If none are passed, stats * based on all nodes will be returned. @@ -59,9 +65,20 @@ public ClusterStatsRequest(String... nodesIds) { super(nodesIds); } + public boolean useOptimizedClusterStatsResponse() { + return useOptimizedClusterStatsResponse; + } + + public void useOptimizedClusterStatsResponse(boolean useOptimizedClusterStatsResponse) { + this.useOptimizedClusterStatsResponse = useOptimizedClusterStatsResponse; + } + @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); + if (out.getVersion().onOrAfter(Version.V_3_0_0)) { + out.writeOptionalBoolean(useOptimizedClusterStatsResponse); + } } } 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 0dcb03dc26d0e..3a07382cbf93e 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 @@ -50,4 +50,9 @@ public class ClusterStatsRequestBuilder extends NodesOperationRequestBuilder< public ClusterStatsRequestBuilder(OpenSearchClient client, ClusterStatsAction action) { super(client, action, new ClusterStatsRequest()); } + + public final ClusterStatsRequestBuilder useOptimizedClusterStatsResponse(boolean useOptimizedClusterStatsResponse) { + request.useOptimizedClusterStatsResponse(useOptimizedClusterStatsResponse); + return (ClusterStatsRequestBuilder) this; + } } diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/stats/NodeIndexShardStats.java b/server/src/main/java/org/opensearch/action/admin/cluster/stats/NodeIndexShardStats.java new file mode 100644 index 0000000000000..e782e21730125 --- /dev/null +++ b/server/src/main/java/org/opensearch/action/admin/cluster/stats/NodeIndexShardStats.java @@ -0,0 +1,98 @@ +/* + * 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.action.admin.indices.stats.CommonStats; +import org.opensearch.action.admin.indices.stats.ShardStats; +import org.opensearch.action.support.nodes.BaseNodeResponse; +import org.opensearch.cluster.node.DiscoveryNode; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.index.cache.query.QueryCacheStats; +import org.opensearch.index.engine.SegmentsStats; +import org.opensearch.index.fielddata.FieldDataStats; +import org.opensearch.index.shard.DocsStats; +import org.opensearch.index.store.StoreStats; +import org.opensearch.search.suggest.completion.CompletionStats; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Node level statistics used for ClusterStatsIndices for _cluster/stats call. + */ +public class NodeIndexShardStats extends BaseNodeResponse { + + DocsStats docs; + StoreStats store; + FieldDataStats fieldData; + QueryCacheStats queryCache; + CompletionStats completion; + SegmentsStats segments; + Map indexStatsMap; + + protected NodeIndexShardStats(StreamInput in) throws IOException { + super(in); + docs = in.readOptionalWriteable(DocsStats::new); + store = in.readOptionalWriteable(StoreStats::new); + fieldData = in.readOptionalWriteable(FieldDataStats::new); + queryCache = in.readOptionalWriteable(QueryCacheStats::new); + completion = in.readOptionalWriteable(CompletionStats::new); + segments = in.readOptionalWriteable(SegmentsStats::new); + indexStatsMap = in.readMap(StreamInput::readString, ClusterStatsIndices.ShardStats::new); + } + + protected NodeIndexShardStats(DiscoveryNode node, ShardStats[] indexShardsStats) { + super(node); + + this.docs = new DocsStats(); + this.store = new StoreStats(); + this.fieldData = new FieldDataStats(); + this.queryCache = new QueryCacheStats(); + this.completion = new CompletionStats(); + this.segments = new SegmentsStats(); + this.indexStatsMap = new HashMap<>(); + + // Index Level Stats + for (org.opensearch.action.admin.indices.stats.ShardStats shardStats : indexShardsStats) { + ClusterStatsIndices.ShardStats indexShardStats = this.indexStatsMap.get(shardStats.getShardRouting().getIndexName()); + if (indexShardStats == null) { + indexShardStats = new ClusterStatsIndices.ShardStats(); + this.indexStatsMap.put(shardStats.getShardRouting().getIndexName(), indexShardStats); + } + + indexShardStats.total++; + + CommonStats shardCommonStats = shardStats.getStats(); + + if (shardStats.getShardRouting().primary()) { + indexShardStats.primaries++; + this.docs.add(shardCommonStats.docs); + } + this.store.add(shardCommonStats.store); + this.fieldData.add(shardCommonStats.fieldData); + this.queryCache.add(shardCommonStats.queryCache); + this.completion.add(shardCommonStats.completion); + this.segments.add(shardCommonStats.segments); + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeOptionalWriteable(docs); + out.writeOptionalWriteable(store); + out.writeOptionalWriteable(fieldData); + out.writeOptionalWriteable(queryCache); + out.writeOptionalWriteable(completion); + out.writeOptionalWriteable(segments); + out.writeMap(indexStatsMap, StreamOutput::writeString, (stream, stats) -> stats.writeTo(stream)); + } +} 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 e4f483f796f44..22e4fbcfb3999 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 @@ -212,8 +212,14 @@ protected ClusterStatsNodeResponse nodeOperation(ClusterStatsNodeRequest nodeReq clusterStatus = new ClusterStateHealth(clusterService.state()).getStatus(); } - return new ClusterStatsNodeResponse(nodeInfo.getNode(), clusterStatus, nodeInfo, nodeStats, shardsStats.toArray(new ShardStats[0])); - + return new ClusterStatsNodeResponse( + nodeInfo.getNode(), + clusterStatus, + nodeInfo, + nodeStats, + shardsStats.toArray(new ShardStats[0]), + nodeRequest.request.useOptimizedClusterStatsResponse() + ); } /** 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 0766e838210fa..804fa594b1905 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 @@ -66,6 +66,7 @@ public String getName() { public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { ClusterStatsRequest clusterStatsRequest = new ClusterStatsRequest().nodesIds(request.paramAsStringArray("nodeId", null)); clusterStatsRequest.timeout(request.param("timeout")); + clusterStatsRequest.useOptimizedClusterStatsResponse(true); return channel -> client.admin().cluster().clusterStats(clusterStatsRequest, new NodesResponseRestListener<>(channel)); } diff --git a/server/src/test/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodesTests.java b/server/src/test/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodesTests.java index 40a30342b86b9..70509e053d11e 100644 --- a/server/src/test/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodesTests.java +++ b/server/src/test/java/org/opensearch/action/admin/cluster/stats/ClusterStatsNodesTests.java @@ -32,16 +32,35 @@ package org.opensearch.action.admin.cluster.stats; +import org.opensearch.Version; import org.opensearch.action.admin.cluster.node.info.NodeInfo; import org.opensearch.action.admin.cluster.node.stats.NodeStats; import org.opensearch.action.admin.cluster.node.stats.NodeStatsTests; +import org.opensearch.action.admin.indices.stats.CommonStats; +import org.opensearch.action.admin.indices.stats.CommonStatsFlags; +import org.opensearch.action.admin.indices.stats.ShardStats; import org.opensearch.cluster.node.DiscoveryNode; +import org.opensearch.cluster.routing.ShardRouting; +import org.opensearch.cluster.routing.ShardRoutingState; +import org.opensearch.cluster.routing.TestShardRouting; import org.opensearch.common.network.NetworkModule; import org.opensearch.common.settings.Settings; +import org.opensearch.core.index.Index; import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.index.cache.query.QueryCacheStats; +import org.opensearch.index.engine.SegmentsStats; +import org.opensearch.index.fielddata.FieldDataStats; +import org.opensearch.index.flush.FlushStats; +import org.opensearch.index.shard.DocsStats; +import org.opensearch.index.shard.IndexingStats; +import org.opensearch.index.shard.ShardPath; +import org.opensearch.index.store.StoreStats; +import org.opensearch.search.suggest.completion.CompletionStats; import org.opensearch.test.OpenSearchTestCase; import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -158,6 +177,231 @@ public void testIngestStats() throws Exception { ); } + public void testMultiVersionScenario() { + // Assuming the default behavior will be the type of response expected from a node of version prior to version containing optimized + // output + int numberOfNodes = randomIntBetween(1, 4); + Index testIndex = new Index("test-index", "_na_"); + + List defaultClusterStatsNodeResponses = new ArrayList<>(); + List optimizedClusterStatsNodeResponses = new ArrayList<>(); + + boolean optimiseClusterStats = randomBoolean(); + + for (int i = 0; i < numberOfNodes; i++) { + DiscoveryNode node = new DiscoveryNode("node-" + i, buildNewFakeTransportAddress(), Version.CURRENT); + CommonStats commonStats = createRandomCommonStats(); + ShardStats[] shardStats = createshardStats(node, testIndex, commonStats); + ClusterStatsNodeResponse customClusterStatsResponse = createClusterStatsNodeResponse(node, shardStats, testIndex, true, false); + ClusterStatsNodeResponse customOptimizedClusterStatsResponse = createClusterStatsNodeResponse( + node, + shardStats, + testIndex, + false, + optimiseClusterStats + ); + defaultClusterStatsNodeResponses.add(customClusterStatsResponse); + optimizedClusterStatsNodeResponses.add(customOptimizedClusterStatsResponse); + } + + ClusterStatsIndices defaultClusterStatsIndices = new ClusterStatsIndices(defaultClusterStatsNodeResponses, null, null); + ClusterStatsIndices optimzedClusterStatsIndices = new ClusterStatsIndices(optimizedClusterStatsNodeResponses, null, null); + + assertEquals(defaultClusterStatsIndices.getIndexCount(), optimzedClusterStatsIndices.getIndexCount()); + + assertEquals(defaultClusterStatsIndices.getShards().getIndices(), optimzedClusterStatsIndices.getShards().getIndices()); + assertEquals(defaultClusterStatsIndices.getShards().getTotal(), optimzedClusterStatsIndices.getShards().getTotal()); + assertEquals(defaultClusterStatsIndices.getShards().getPrimaries(), optimzedClusterStatsIndices.getShards().getPrimaries()); + assertEquals( + defaultClusterStatsIndices.getShards().getMinIndexShards(), + optimzedClusterStatsIndices.getShards().getMaxIndexShards() + ); + assertEquals( + defaultClusterStatsIndices.getShards().getMinIndexPrimaryShards(), + optimzedClusterStatsIndices.getShards().getMinIndexPrimaryShards() + ); + + // As AssertEquals with double is deprecated and can only be used to compare floating-point numbers + assertTrue(defaultClusterStatsIndices.getShards().getReplication() == optimzedClusterStatsIndices.getShards().getReplication()); + assertTrue( + defaultClusterStatsIndices.getShards().getAvgIndexShards() == optimzedClusterStatsIndices.getShards().getAvgIndexShards() + ); + assertTrue( + defaultClusterStatsIndices.getShards().getMaxIndexPrimaryShards() == optimzedClusterStatsIndices.getShards() + .getMaxIndexPrimaryShards() + ); + assertTrue( + defaultClusterStatsIndices.getShards().getAvgIndexPrimaryShards() == optimzedClusterStatsIndices.getShards() + .getAvgIndexPrimaryShards() + ); + assertTrue( + defaultClusterStatsIndices.getShards().getMinIndexReplication() == optimzedClusterStatsIndices.getShards() + .getMinIndexReplication() + ); + assertTrue( + defaultClusterStatsIndices.getShards().getAvgIndexReplication() == optimzedClusterStatsIndices.getShards() + .getAvgIndexReplication() + ); + assertTrue( + defaultClusterStatsIndices.getShards().getMaxIndexReplication() == optimzedClusterStatsIndices.getShards() + .getMaxIndexReplication() + ); + + // Docs stats + assertEquals( + defaultClusterStatsIndices.getDocs().getAverageSizeInBytes(), + optimzedClusterStatsIndices.getDocs().getAverageSizeInBytes() + ); + assertEquals(defaultClusterStatsIndices.getDocs().getDeleted(), optimzedClusterStatsIndices.getDocs().getDeleted()); + assertEquals(defaultClusterStatsIndices.getDocs().getCount(), optimzedClusterStatsIndices.getDocs().getCount()); + assertEquals( + defaultClusterStatsIndices.getDocs().getTotalSizeInBytes(), + optimzedClusterStatsIndices.getDocs().getTotalSizeInBytes() + ); + + // Store Stats + assertEquals(defaultClusterStatsIndices.getStore().getSizeInBytes(), optimzedClusterStatsIndices.getStore().getSizeInBytes()); + assertEquals(defaultClusterStatsIndices.getStore().getSize(), optimzedClusterStatsIndices.getStore().getSize()); + assertEquals(defaultClusterStatsIndices.getStore().getReservedSize(), optimzedClusterStatsIndices.getStore().getReservedSize()); + + // Query Cache + assertEquals( + defaultClusterStatsIndices.getQueryCache().getCacheCount(), + optimzedClusterStatsIndices.getQueryCache().getCacheCount() + ); + assertEquals(defaultClusterStatsIndices.getQueryCache().getCacheSize(), optimzedClusterStatsIndices.getQueryCache().getCacheSize()); + assertEquals(defaultClusterStatsIndices.getQueryCache().getEvictions(), optimzedClusterStatsIndices.getQueryCache().getEvictions()); + assertEquals(defaultClusterStatsIndices.getQueryCache().getHitCount(), optimzedClusterStatsIndices.getQueryCache().getHitCount()); + assertEquals( + defaultClusterStatsIndices.getQueryCache().getTotalCount(), + optimzedClusterStatsIndices.getQueryCache().getTotalCount() + ); + assertEquals(defaultClusterStatsIndices.getQueryCache().getMissCount(), optimzedClusterStatsIndices.getQueryCache().getMissCount()); + assertEquals( + defaultClusterStatsIndices.getQueryCache().getMemorySize(), + optimzedClusterStatsIndices.getQueryCache().getMemorySize() + ); + assertEquals( + defaultClusterStatsIndices.getQueryCache().getMemorySizeInBytes(), + optimzedClusterStatsIndices.getQueryCache().getMemorySizeInBytes() + ); + + // Completion Stats + assertEquals( + defaultClusterStatsIndices.getCompletion().getSizeInBytes(), + optimzedClusterStatsIndices.getCompletion().getSizeInBytes() + ); + assertEquals(defaultClusterStatsIndices.getCompletion().getSize(), optimzedClusterStatsIndices.getCompletion().getSize()); + + // Segment Stats + assertEquals( + defaultClusterStatsIndices.getSegments().getBitsetMemory(), + optimzedClusterStatsIndices.getSegments().getBitsetMemory() + ); + assertEquals(defaultClusterStatsIndices.getSegments().getCount(), optimzedClusterStatsIndices.getSegments().getCount()); + assertEquals( + defaultClusterStatsIndices.getSegments().getBitsetMemoryInBytes(), + optimzedClusterStatsIndices.getSegments().getBitsetMemoryInBytes() + ); + assertEquals(defaultClusterStatsIndices.getSegments().getFileSizes(), optimzedClusterStatsIndices.getSegments().getFileSizes()); + assertEquals( + defaultClusterStatsIndices.getSegments().getIndexWriterMemoryInBytes(), + optimzedClusterStatsIndices.getSegments().getIndexWriterMemoryInBytes() + ); + assertEquals( + defaultClusterStatsIndices.getSegments().getVersionMapMemory(), + optimzedClusterStatsIndices.getSegments().getVersionMapMemory() + ); + assertEquals( + defaultClusterStatsIndices.getSegments().getVersionMapMemoryInBytes(), + optimzedClusterStatsIndices.getSegments().getVersionMapMemoryInBytes() + ); + + } + + private ClusterStatsNodeResponse createClusterStatsNodeResponse( + DiscoveryNode node, + ShardStats[] shardStats, + Index index, + boolean defaultBehavior, + boolean optimized + ) { + if (defaultBehavior) { + return new ClusterStatsNodeResponse(node, null, null, null, shardStats); + } else { + return new ClusterStatsNodeResponse(node, null, null, null, shardStats, optimized); + } + + } + + private CommonStats createRandomCommonStats() { + CommonStats commonStats = new CommonStats(CommonStatsFlags.NONE); + commonStats.docs = new DocsStats(randomLongBetween(0, 10000), randomLongBetween(0, 100), randomLongBetween(0, 1000)); + commonStats.store = new StoreStats(randomLongBetween(0, 100), randomLongBetween(0, 1000)); + commonStats.indexing = new IndexingStats(); + commonStats.completion = new CompletionStats(); + commonStats.flush = new FlushStats(randomLongBetween(0, 100), randomLongBetween(0, 100), randomLongBetween(0, 100)); + commonStats.fieldData = new FieldDataStats(randomLongBetween(0, 100), randomLongBetween(0, 100), null); + commonStats.queryCache = new QueryCacheStats( + randomLongBetween(0, 100), + randomLongBetween(0, 100), + randomLongBetween(0, 100), + randomLongBetween(0, 100), + randomLongBetween(0, 100) + ); + commonStats.segments = new SegmentsStats(); + + return commonStats; + } + + private ShardStats[] createshardStats(DiscoveryNode localNode, Index index, CommonStats commonStats) { + List shardStatsList = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + ShardRoutingState shardRoutingState = ShardRoutingState.fromValue((byte) randomIntBetween(2, 3)); + ShardRouting shardRouting = TestShardRouting.newShardRouting( + index.getName(), + i, + localNode.getId(), + randomBoolean(), + shardRoutingState + ); + + Path path = createTempDir().resolve("indices") + .resolve(shardRouting.shardId().getIndex().getUUID()) + .resolve(String.valueOf(shardRouting.shardId().id())); + + ShardStats shardStats = new ShardStats( + shardRouting, + new ShardPath(false, path, path, shardRouting.shardId()), + commonStats, + null, + null, + null + ); + shardStatsList.add(shardStats); + } + + return shardStatsList.toArray(new ShardStats[0]); + } + + private class MockShardStats extends ClusterStatsIndices.ShardStats { + public boolean equals(ClusterStatsIndices.ShardStats shardStats) { + return this.getIndices() == shardStats.getIndices() + && this.getTotal() == shardStats.getTotal() + && this.getPrimaries() == shardStats.getPrimaries() + && this.getReplication() == shardStats.getReplication() + && this.getMaxIndexShards() == shardStats.getMaxIndexShards() + && this.getMinIndexShards() == shardStats.getMinIndexShards() + && this.getAvgIndexShards() == shardStats.getAvgIndexShards() + && this.getMaxIndexPrimaryShards() == shardStats.getMaxIndexPrimaryShards() + && this.getMinIndexPrimaryShards() == shardStats.getMinIndexPrimaryShards() + && this.getAvgIndexPrimaryShards() == shardStats.getAvgIndexPrimaryShards() + && this.getMinIndexReplication() == shardStats.getMinIndexReplication() + && this.getAvgIndexReplication() == shardStats.getAvgIndexReplication() + && this.getMaxIndexReplication() == shardStats.getMaxIndexReplication(); + } + } + private static NodeInfo createNodeInfo(String nodeId, String transportType, String httpType) { Settings.Builder settings = Settings.builder(); if (transportType != null) {