diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketMessageBrokerStats.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketMessageBrokerStats.java index 6b35ac0398..7adf7c3fe7 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketMessageBrokerStats.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketMessageBrokerStats.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.time.Duration; import java.time.Instant; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; @@ -49,6 +50,7 @@ import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; * the {@link org.springframework.jmx.export.MBeanExporter MBeanExporter}. * * @author Rossen Stoyanchev + * @author Sam Brannen * @since 4.1 */ public class WebSocketMessageBrokerStats { @@ -174,16 +176,14 @@ public class WebSocketMessageBrokerStats { * Get stats about the executor processing incoming messages from WebSocket clients. */ public String getClientInboundExecutorStatsInfo() { - return (this.inboundChannelExecutor != null ? - getExecutorStatsInfo(this.inboundChannelExecutor) : "null"); + return getExecutorStatsInfo(this.inboundChannelExecutor); } /** * Get stats about the executor processing outgoing messages to WebSocket clients. */ public String getClientOutboundExecutorStatsInfo() { - return (this.outboundChannelExecutor != null ? - getExecutorStatsInfo(this.outboundChannelExecutor) : "null"); + return getExecutorStatsInfo(this.outboundChannelExecutor); } /** @@ -197,16 +197,31 @@ public class WebSocketMessageBrokerStats { return getExecutorStatsInfo(((ThreadPoolTaskScheduler) this.sockJsTaskScheduler) .getScheduledThreadPoolExecutor()); } - else { - return "unknown"; - } + return "unknown"; } - private String getExecutorStatsInfo(Executor executor) { - executor = executor instanceof ThreadPoolTaskExecutor ? - ((ThreadPoolTaskExecutor) executor).getThreadPoolExecutor() : executor; - String str = executor.toString(); - return str.substring(str.indexOf("pool"), str.length() - 1); + private String getExecutorStatsInfo(@Nullable Executor executor) { + if (executor == null) { + return "null"; + } + + if (executor instanceof ThreadPoolTaskExecutor) { + executor = ((ThreadPoolTaskExecutor) executor).getThreadPoolExecutor(); + } + + if (executor instanceof ThreadPoolExecutor) { + // It is assumed that the implementation of toString() in ThreadPoolExecutor + // generates text that ends similar to the following: + // pool size = #, active threads = #, queued tasks = #, completed tasks = #] + String str = executor.toString(); + int indexOfPool = str.indexOf("pool"); + if (indexOfPool != -1) { + // (length - 1) omits the trailing "]" + return str.substring(indexOfPool, str.length() - 1); + } + } + + return "unknown"; } @Override diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/WebSocketMessageBrokerStatsTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/WebSocketMessageBrokerStatsTests.java new file mode 100644 index 0000000000..324dc4942d --- /dev/null +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/WebSocketMessageBrokerStatsTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.web.socket.config; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for {@link WebSocketMessageBrokerStats}. + * + * @author Sam Brannen + * @since 5.3.10 + * @see org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurationSupportTests + */ +class WebSocketMessageBrokerStatsTests { + + private final WebSocketMessageBrokerStats stats = new WebSocketMessageBrokerStats(); + + @Test + void nullValues() { + String expected = "WebSocketSession[null], stompSubProtocol[null], stompBrokerRelay[null], " + + "inboundChannel[null], outboundChannel[null], sockJsScheduler[null]"; + assertThat(stats).hasToString(expected); + } + + @Test + void inboundAndOutboundChannelsWithThreadPoolTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.afterPropertiesSet(); + + stats.setInboundChannelExecutor(executor); + stats.setOutboundChannelExecutor(executor); + + assertThat(stats.getClientInboundExecutorStatsInfo()).as("inbound channel stats") + .isEqualTo("pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0"); + assertThat(stats.getClientOutboundExecutorStatsInfo()).as("outbound channel stats") + .isEqualTo("pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0"); + } + + @Test + void inboundAndOutboundChannelsWithMockedTaskExecutor() { + TaskExecutor executor = mock(TaskExecutor.class); + + stats.setInboundChannelExecutor(executor); + stats.setOutboundChannelExecutor(executor); + + assertThat(stats.getClientInboundExecutorStatsInfo()).as("inbound channel stats").isEqualTo("unknown"); + assertThat(stats.getClientOutboundExecutorStatsInfo()).as("outbound channel stats").isEqualTo("unknown"); + } + + @Test + void sockJsTaskSchedulerWithThreadPoolTaskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.afterPropertiesSet(); + + stats.setSockJsTaskScheduler(scheduler); + + assertThat(stats.getSockJsTaskSchedulerStatsInfo()) + .isEqualTo("pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0"); + } + + @Test + void sockJsTaskSchedulerWithMockedTaskScheduler() { + TaskScheduler scheduler = mock(TaskScheduler.class); + + stats.setSockJsTaskScheduler(scheduler); + + assertThat(stats.getSockJsTaskSchedulerStatsInfo()).isEqualTo("unknown"); + } + +}