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}