|
8 | 8 | */ |
9 | 9 | package org.elasticsearch.index.shard; |
10 | 10 |
|
| 11 | +import org.apache.logging.log4j.LogManager; |
| 12 | +import org.apache.logging.log4j.Logger; |
11 | 13 | import org.apache.lucene.index.DirectoryReader; |
12 | 14 | import org.apache.lucene.store.LockObtainFailedException; |
13 | 15 | import org.apache.lucene.util.SetOnce; |
|
22 | 24 | import org.elasticsearch.cluster.EstimatedHeapUsage; |
23 | 25 | import org.elasticsearch.cluster.EstimatedHeapUsageCollector; |
24 | 26 | import org.elasticsearch.cluster.InternalClusterInfoService; |
| 27 | +import org.elasticsearch.cluster.NodeUsageStatsForThreadPools; |
| 28 | +import org.elasticsearch.cluster.NodeUsageStatsForThreadPoolsCollector; |
25 | 29 | import org.elasticsearch.cluster.metadata.IndexMetadata; |
26 | 30 | import org.elasticsearch.cluster.node.DiscoveryNode; |
27 | 31 | import org.elasticsearch.cluster.node.DiscoveryNodeUtils; |
28 | 32 | import org.elasticsearch.cluster.routing.RecoverySource; |
29 | 33 | import org.elasticsearch.cluster.routing.ShardRouting; |
30 | 34 | import org.elasticsearch.cluster.routing.ShardRoutingState; |
31 | 35 | import org.elasticsearch.cluster.routing.UnassignedInfo; |
| 36 | +import org.elasticsearch.cluster.routing.allocation.WriteLoadConstraintSettings; |
32 | 37 | import org.elasticsearch.cluster.service.ClusterService; |
33 | 38 | import org.elasticsearch.common.Strings; |
34 | 39 | import org.elasticsearch.common.UUIDs; |
|
73 | 78 | import org.elasticsearch.test.ESSingleNodeTestCase; |
74 | 79 | import org.elasticsearch.test.IndexSettingsModule; |
75 | 80 | import org.elasticsearch.test.InternalSettingsPlugin; |
| 81 | +import org.elasticsearch.threadpool.ThreadPool; |
76 | 82 | import org.elasticsearch.xcontent.XContentType; |
77 | 83 | import org.junit.Assert; |
78 | 84 |
|
|
85 | 91 | import java.util.Arrays; |
86 | 92 | import java.util.Collection; |
87 | 93 | import java.util.Collections; |
| 94 | +import java.util.HashMap; |
88 | 95 | import java.util.List; |
89 | 96 | import java.util.Locale; |
90 | 97 | import java.util.Map; |
|
117 | 124 | import static org.hamcrest.Matchers.either; |
118 | 125 | import static org.hamcrest.Matchers.equalTo; |
119 | 126 | import static org.hamcrest.Matchers.greaterThan; |
| 127 | +import static org.hamcrest.Matchers.greaterThanOrEqualTo; |
120 | 128 | import static org.hamcrest.Matchers.instanceOf; |
121 | 129 | import static org.hamcrest.Matchers.lessThanOrEqualTo; |
122 | 130 |
|
123 | 131 | public class IndexShardIT extends ESSingleNodeTestCase { |
| 132 | + private static final Logger logger = LogManager.getLogger(IndexShardIT.class); |
124 | 133 |
|
125 | 134 | @Override |
126 | 135 | protected Collection<Class<? extends Plugin>> getPlugins() { |
127 | | - return pluginList(InternalSettingsPlugin.class, BogusEstimatedHeapUsagePlugin.class); |
| 136 | + return pluginList( |
| 137 | + InternalSettingsPlugin.class, |
| 138 | + BogusEstimatedHeapUsagePlugin.class, |
| 139 | + BogusNodeUsageStatsForThreadPoolsCollectorPlugin.class |
| 140 | + ); |
128 | 141 | } |
129 | 142 |
|
130 | 143 | public void testLockTryingToDelete() throws Exception { |
@@ -295,6 +308,53 @@ public void testHeapUsageEstimateIsPresent() { |
295 | 308 | } |
296 | 309 | } |
297 | 310 |
|
| 311 | + public void testNodeWriteLoadsArePresent() { |
| 312 | + InternalClusterInfoService clusterInfoService = (InternalClusterInfoService) getInstanceFromNode(ClusterInfoService.class); |
| 313 | + ClusterInfoServiceUtils.refresh(clusterInfoService); |
| 314 | + Map<String, NodeUsageStatsForThreadPools> nodeThreadPoolStats = clusterInfoService.getClusterInfo() |
| 315 | + .getNodeUsageStatsForThreadPools(); |
| 316 | + assertNotNull(nodeThreadPoolStats); |
| 317 | + /** Not collecting stats yet because allocation write load stats collection is disabled by default. |
| 318 | + * see {@link WriteLoadConstraintSettings.WRITE_LOAD_DECIDER_ENABLED_SETTING} */ |
| 319 | + assertTrue(nodeThreadPoolStats.isEmpty()); |
| 320 | + |
| 321 | + // Enable collection for node write loads. |
| 322 | + updateClusterSettings( |
| 323 | + Settings.builder() |
| 324 | + .put( |
| 325 | + WriteLoadConstraintSettings.WRITE_LOAD_DECIDER_ENABLED_SETTING.getKey(), |
| 326 | + WriteLoadConstraintSettings.WriteLoadDeciderStatus.ENABLED |
| 327 | + ) |
| 328 | + .build() |
| 329 | + ); |
| 330 | + try { |
| 331 | + // Force a ClusterInfo refresh to run collection of the node thread pool usage stats. |
| 332 | + ClusterInfoServiceUtils.refresh(clusterInfoService); |
| 333 | + nodeThreadPoolStats = clusterInfoService.getClusterInfo().getNodeUsageStatsForThreadPools(); |
| 334 | + |
| 335 | + /** Verify that each node has usage stats reported. The test {@link BogusNodeUsageStatsForThreadPoolsCollector} implementation |
| 336 | + * generates random usage values */ |
| 337 | + ClusterState state = getInstanceFromNode(ClusterService.class).state(); |
| 338 | + assertEquals(state.nodes().size(), nodeThreadPoolStats.size()); |
| 339 | + for (DiscoveryNode node : state.nodes()) { |
| 340 | + assertTrue(nodeThreadPoolStats.containsKey(node.getId())); |
| 341 | + NodeUsageStatsForThreadPools nodeUsageStatsForThreadPools = nodeThreadPoolStats.get(node.getId()); |
| 342 | + assertThat(nodeUsageStatsForThreadPools.nodeId(), equalTo(node.getId())); |
| 343 | + NodeUsageStatsForThreadPools.ThreadPoolUsageStats writeThreadPoolStats = nodeUsageStatsForThreadPools |
| 344 | + .threadPoolUsageStatsMap() |
| 345 | + .get(ThreadPool.Names.WRITE); |
| 346 | + assertNotNull(writeThreadPoolStats); |
| 347 | + assertThat(writeThreadPoolStats.totalThreadPoolThreads(), greaterThanOrEqualTo(0)); |
| 348 | + assertThat(writeThreadPoolStats.averageThreadPoolUtilization(), greaterThanOrEqualTo(0.0f)); |
| 349 | + assertThat(writeThreadPoolStats.averageThreadPoolQueueLatencyMillis(), greaterThanOrEqualTo(0L)); |
| 350 | + } |
| 351 | + } finally { |
| 352 | + updateClusterSettings( |
| 353 | + Settings.builder().putNull(WriteLoadConstraintSettings.WRITE_LOAD_DECIDER_ENABLED_SETTING.getKey()).build() |
| 354 | + ); |
| 355 | + } |
| 356 | + } |
| 357 | + |
298 | 358 | public void testIndexCanChangeCustomDataPath() throws Exception { |
299 | 359 | final String index = "test-custom-data-path"; |
300 | 360 | final Path sharedDataPath = getInstanceFromNode(Environment.class).sharedDataDir().resolve(randomAsciiLettersOfLength(10)); |
@@ -875,4 +935,61 @@ public ClusterService getClusterService() { |
875 | 935 | return clusterService.get(); |
876 | 936 | } |
877 | 937 | } |
| 938 | + |
| 939 | + /** |
| 940 | + * A simple {@link NodeUsageStatsForThreadPoolsCollector} implementation that creates and returns random |
| 941 | + * {@link NodeUsageStatsForThreadPools} for each node in the cluster. |
| 942 | + * <p> |
| 943 | + * Note: there's an 'org.elasticsearch.cluster.NodeUsageStatsForThreadPoolsCollector' file that declares this implementation so that the |
| 944 | + * plugin system can pick it up and use it for the test set-up. |
| 945 | + */ |
| 946 | + public static class BogusNodeUsageStatsForThreadPoolsCollector implements NodeUsageStatsForThreadPoolsCollector { |
| 947 | + |
| 948 | + private final BogusNodeUsageStatsForThreadPoolsCollectorPlugin plugin; |
| 949 | + |
| 950 | + public BogusNodeUsageStatsForThreadPoolsCollector(BogusNodeUsageStatsForThreadPoolsCollectorPlugin plugin) { |
| 951 | + this.plugin = plugin; |
| 952 | + } |
| 953 | + |
| 954 | + @Override |
| 955 | + public void collectUsageStats(ActionListener<Map<String, NodeUsageStatsForThreadPools>> listener) { |
| 956 | + ActionListener.completeWith( |
| 957 | + listener, |
| 958 | + () -> plugin.getClusterService() |
| 959 | + .state() |
| 960 | + .nodes() |
| 961 | + .stream() |
| 962 | + .collect(Collectors.toUnmodifiableMap(DiscoveryNode::getId, node -> makeRandomNodeUsageStats(node.getId()))) |
| 963 | + ); |
| 964 | + } |
| 965 | + |
| 966 | + private NodeUsageStatsForThreadPools makeRandomNodeUsageStats(String nodeId) { |
| 967 | + NodeUsageStatsForThreadPools.ThreadPoolUsageStats writeThreadPoolStats = new NodeUsageStatsForThreadPools.ThreadPoolUsageStats( |
| 968 | + randomNonNegativeInt(), |
| 969 | + randomFloat(), |
| 970 | + randomNonNegativeLong() |
| 971 | + ); |
| 972 | + Map<String, NodeUsageStatsForThreadPools.ThreadPoolUsageStats> statsForThreadPools = new HashMap<>(); |
| 973 | + statsForThreadPools.put(ThreadPool.Names.WRITE, writeThreadPoolStats); |
| 974 | + return new NodeUsageStatsForThreadPools(nodeId, statsForThreadPools); |
| 975 | + } |
| 976 | + } |
| 977 | + |
| 978 | + /** |
| 979 | + * Make a plugin to gain access to the {@link ClusterService} instance. |
| 980 | + */ |
| 981 | + public static class BogusNodeUsageStatsForThreadPoolsCollectorPlugin extends Plugin implements ClusterPlugin { |
| 982 | + |
| 983 | + private final SetOnce<ClusterService> clusterService = new SetOnce<>(); |
| 984 | + |
| 985 | + @Override |
| 986 | + public Collection<?> createComponents(PluginServices services) { |
| 987 | + clusterService.set(services.clusterService()); |
| 988 | + return List.of(); |
| 989 | + } |
| 990 | + |
| 991 | + public ClusterService getClusterService() { |
| 992 | + return clusterService.get(); |
| 993 | + } |
| 994 | + } |
878 | 995 | } |
0 commit comments