001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.socket.config; 018 019import java.util.concurrent.Executor; 020import java.util.concurrent.ScheduledFuture; 021import java.util.concurrent.ScheduledThreadPoolExecutor; 022import java.util.concurrent.ThreadPoolExecutor; 023import java.util.concurrent.TimeUnit; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027 028import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler; 029import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 030import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 031import org.springframework.web.socket.messaging.StompSubProtocolHandler; 032import org.springframework.web.socket.messaging.SubProtocolHandler; 033import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; 034 035/** 036 * A central class for aggregating information about internal state and counters 037 * from key infrastructure components of the setup that comes with 038 * {@code @EnableWebSocketMessageBroker} for Java config and 039 * {@code <websocket:message-broker>} for XML. 040 * 041 * <p>By default aggregated information is logged every 30 minutes at INFO level. 042 * The frequency of logging can be changed via {@link #setLoggingPeriod(long)}. 043 * 044 * <p>This class is declared as a Spring bean by the above configuration with the 045 * name "webSocketMessageBrokerStats" and can be easily exported to JMX, e.g. with 046 * the {@link org.springframework.jmx.export.MBeanExporter MBeanExporter}. 047 * 048 * @author Rossen Stoyanchev 049 * @since 4.1 050 */ 051public class WebSocketMessageBrokerStats { 052 053 private static final Log logger = LogFactory.getLog(WebSocketMessageBrokerStats.class); 054 055 056 private SubProtocolWebSocketHandler webSocketHandler; 057 058 private StompSubProtocolHandler stompSubProtocolHandler; 059 060 private StompBrokerRelayMessageHandler stompBrokerRelay; 061 062 private ThreadPoolExecutor inboundChannelExecutor; 063 064 private ThreadPoolExecutor outboundChannelExecutor; 065 066 private ScheduledThreadPoolExecutor sockJsTaskScheduler; 067 068 private ScheduledFuture<?> loggingTask; 069 070 private long loggingPeriod = 30 * 60 * 1000; 071 072 073 public void setSubProtocolWebSocketHandler(SubProtocolWebSocketHandler webSocketHandler) { 074 this.webSocketHandler = webSocketHandler; 075 this.stompSubProtocolHandler = initStompSubProtocolHandler(); 076 } 077 078 private StompSubProtocolHandler initStompSubProtocolHandler() { 079 for (SubProtocolHandler handler : this.webSocketHandler.getProtocolHandlers()) { 080 if (handler instanceof StompSubProtocolHandler) { 081 return (StompSubProtocolHandler) handler; 082 } 083 } 084 SubProtocolHandler defaultHandler = this.webSocketHandler.getDefaultProtocolHandler(); 085 if (defaultHandler != null && defaultHandler instanceof StompSubProtocolHandler) { 086 return (StompSubProtocolHandler) defaultHandler; 087 } 088 return null; 089 } 090 091 public void setStompBrokerRelay(StompBrokerRelayMessageHandler stompBrokerRelay) { 092 this.stompBrokerRelay = stompBrokerRelay; 093 } 094 095 public void setInboundChannelExecutor(ThreadPoolTaskExecutor inboundChannelExecutor) { 096 this.inboundChannelExecutor = inboundChannelExecutor.getThreadPoolExecutor(); 097 } 098 099 public void setOutboundChannelExecutor(ThreadPoolTaskExecutor outboundChannelExecutor) { 100 this.outboundChannelExecutor = outboundChannelExecutor.getThreadPoolExecutor(); 101 } 102 103 public void setSockJsTaskScheduler(ThreadPoolTaskScheduler sockJsTaskScheduler) { 104 this.sockJsTaskScheduler = sockJsTaskScheduler.getScheduledThreadPoolExecutor(); 105 this.loggingTask = initLoggingTask(1 * 60 * 1000); 106 } 107 108 private ScheduledFuture<?> initLoggingTask(long initialDelay) { 109 if (this.loggingPeriod > 0 && logger.isInfoEnabled()) { 110 return this.sockJsTaskScheduler.scheduleAtFixedRate(new Runnable() { 111 @Override 112 public void run() { 113 logger.info(WebSocketMessageBrokerStats.this); 114 } 115 }, initialDelay, this.loggingPeriod, TimeUnit.MILLISECONDS); 116 } 117 return null; 118 } 119 120 /** 121 * Set the frequency for logging information at INFO level in milliseconds. 122 * If set 0 or less than 0, the logging task is cancelled. 123 * <p>By default this property is set to 30 minutes (30 * 60 * 1000). 124 */ 125 public void setLoggingPeriod(long period) { 126 if (this.loggingTask != null) { 127 this.loggingTask.cancel(true); 128 } 129 this.loggingPeriod = period; 130 this.loggingTask = initLoggingTask(0); 131 } 132 133 /** 134 * Return the configured logging period frequency in milliseconds. 135 */ 136 public long getLoggingPeriod() { 137 return this.loggingPeriod; 138 } 139 140 /** 141 * Get stats about WebSocket sessions. 142 */ 143 public String getWebSocketSessionStatsInfo() { 144 return (this.webSocketHandler != null ? this.webSocketHandler.getStatsInfo() : "null"); 145 } 146 147 /** 148 * Get stats about STOMP-related WebSocket message processing. 149 */ 150 public String getStompSubProtocolStatsInfo() { 151 return (this.stompSubProtocolHandler != null ? this.stompSubProtocolHandler.getStatsInfo() : "null"); 152 } 153 154 /** 155 * Get stats about STOMP broker relay (when using a full-featured STOMP broker). 156 */ 157 public String getStompBrokerRelayStatsInfo() { 158 return (this.stompBrokerRelay != null ? this.stompBrokerRelay.getStatsInfo() : "null"); 159 } 160 161 /** 162 * Get stats about the executor processing incoming messages from WebSocket clients. 163 */ 164 public String getClientInboundExecutorStatsInfo() { 165 return (this.inboundChannelExecutor != null ? getExecutorStatsInfo(this.inboundChannelExecutor) : "null"); 166 } 167 168 /** 169 * Get stats about the executor processing outgoing messages to WebSocket clients. 170 */ 171 public String getClientOutboundExecutorStatsInfo() { 172 return (this.outboundChannelExecutor != null ? getExecutorStatsInfo(this.outboundChannelExecutor) : "null"); 173 } 174 175 /** 176 * Get stats about the SockJS task scheduler. 177 */ 178 public String getSockJsTaskSchedulerStatsInfo() { 179 return (this.sockJsTaskScheduler != null ? getExecutorStatsInfo(this.sockJsTaskScheduler) : "null"); 180 } 181 182 private String getExecutorStatsInfo(Executor executor) { 183 String str = executor.toString(); 184 return str.substring(str.indexOf("pool"), str.length() - 1); 185 } 186 187 public String toString() { 188 return "WebSocketSession[" + getWebSocketSessionStatsInfo() + "]" + 189 ", stompSubProtocol[" + getStompSubProtocolStatsInfo() + "]" + 190 ", stompBrokerRelay[" + getStompBrokerRelayStatsInfo() + "]" + 191 ", inboundChannel[" + getClientInboundExecutorStatsInfo() + "]" + 192 ", outboundChannel[" + getClientOutboundExecutorStatsInfo() + "]" + 193 ", sockJsScheduler[" + getSockJsTaskSchedulerStatsInfo() + "]"; 194 } 195 196}