001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.plugin;
018
019import java.io.File;
020import java.net.URI;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.Set;
024
025import javax.jms.JMSException;
026import javax.management.ObjectName;
027
028import org.apache.activemq.advisory.AdvisorySupport;
029import org.apache.activemq.broker.Broker;
030import org.apache.activemq.broker.BrokerFilter;
031import org.apache.activemq.broker.BrokerService;
032import org.apache.activemq.broker.ConnectionContext;
033import org.apache.activemq.broker.ProducerBrokerExchange;
034import org.apache.activemq.broker.jmx.BrokerViewMBean;
035import org.apache.activemq.broker.jmx.SubscriptionViewMBean;
036import org.apache.activemq.broker.region.Destination;
037import org.apache.activemq.broker.region.DestinationStatistics;
038import org.apache.activemq.broker.region.Queue;
039import org.apache.activemq.broker.region.RegionBroker;
040import org.apache.activemq.broker.region.Topic;
041import org.apache.activemq.command.ActiveMQDestination;
042import org.apache.activemq.command.ActiveMQMapMessage;
043import org.apache.activemq.command.Message;
044import org.apache.activemq.command.MessageId;
045import org.apache.activemq.command.ProducerId;
046import org.apache.activemq.command.ProducerInfo;
047import org.apache.activemq.state.ProducerState;
048import org.apache.activemq.usage.SystemUsage;
049import org.apache.activemq.util.IdGenerator;
050import org.apache.activemq.util.LongSequenceGenerator;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053/**
054 * A StatisticsBroker You can retrieve a Map Message for a Destination - or
055 * Broker containing statistics as key-value pairs The message must contain a
056 * replyTo Destination - else its ignored
057 *
058 */
059public class StatisticsBroker extends BrokerFilter {
060    private static Logger LOG = LoggerFactory.getLogger(StatisticsBroker.class);
061    static final String STATS_PREFIX = "ActiveMQ.Statistics";
062
063    static final String STATS_DESTINATION_PREFIX = "ActiveMQ.Statistics.Destination";
064    static final String STATS_BROKER_PREFIX = "ActiveMQ.Statistics.Broker";
065    static final String STATS_BROKER_RESET_HEADER = "ActiveMQ.Statistics.Broker.Reset";
066    static final String STATS_SUBSCRIPTION_PREFIX = "ActiveMQ.Statistics.Subscription";
067
068    // Query-message properties controlling features of Destination-query replies:
069    static final String STATS_DENOTE_END_LIST = "ActiveMQ.Statistics.Destination.List.End.With.Null";
070    static final String STATS_FIRST_MESSAGE_TIMESTAMP = "ActiveMQ.Statistics.Destination.Include.First.Message.Timestamp";
071
072    private static final IdGenerator ID_GENERATOR = new IdGenerator();
073    private final LongSequenceGenerator messageIdGenerator = new LongSequenceGenerator();
074    protected final ProducerId advisoryProducerId = new ProducerId();
075    protected BrokerViewMBean brokerView;
076
077    /**
078     *
079     * Constructor
080     *
081     * @param next
082     */
083    public StatisticsBroker(Broker next) {
084        super(next);
085        this.advisoryProducerId.setConnectionId(ID_GENERATOR.generateId());
086    }
087
088    /**
089     * Sets the persistence mode
090     *
091     * @see org.apache.activemq.broker.BrokerFilter#send(org.apache.activemq.broker.ProducerBrokerExchange,
092     *      org.apache.activemq.command.Message)
093     */
094    @Override
095    public void send(ProducerBrokerExchange producerExchange, Message messageSend) throws Exception {
096        ActiveMQDestination msgDest = messageSend.getDestination();
097        ActiveMQDestination replyTo = messageSend.getReplyTo();
098        if ((replyTo != null) && (msgDest.getPhysicalName().startsWith(STATS_PREFIX))) {
099            String physicalName = msgDest.getPhysicalName();
100            boolean destStats = physicalName.startsWith(STATS_DESTINATION_PREFIX);
101            boolean brokerStats = physicalName.startsWith(STATS_BROKER_PREFIX);
102            boolean subStats = physicalName.startsWith(STATS_SUBSCRIPTION_PREFIX);
103            BrokerService brokerService = getBrokerService();
104            RegionBroker regionBroker = (RegionBroker) brokerService.getRegionBroker();
105            if (destStats) {
106                String destinationName = physicalName.substring(STATS_DESTINATION_PREFIX.length());
107                if (destinationName.startsWith(".")) {
108                    destinationName = destinationName.substring(1);
109                }
110                String destinationQuery = destinationName.replace(STATS_DENOTE_END_LIST,"");
111                boolean endListMessage = !destinationName.equals(destinationQuery)
112                        || messageSend.getProperties().containsKey(STATS_DENOTE_END_LIST);
113                ActiveMQDestination queryDestination = ActiveMQDestination.createDestination(destinationQuery,msgDest.getDestinationType());
114                Set<Destination> destinations = getDestinations(queryDestination);
115
116                boolean includeFirstMessageTimestamp = messageSend.getProperties().containsKey(STATS_FIRST_MESSAGE_TIMESTAMP);
117                List<Message> tempFirstMessage = includeFirstMessageTimestamp ? new ArrayList<>(1) : null;
118
119                for (Destination dest : destinations) {
120                    DestinationStatistics stats = dest.getDestinationStatistics();
121                    if (stats != null) {
122                        ActiveMQMapMessage statsMessage = new ActiveMQMapMessage();
123                        statsMessage.setString("brokerName", regionBroker.getBrokerName());
124                        statsMessage.setString("brokerId", regionBroker.getBrokerId().toString());
125                        statsMessage.setString("destinationName", dest.getActiveMQDestination().toString());
126                        statsMessage.setLong("size", stats.getMessages().getCount());
127                        statsMessage.setLong("enqueueCount", stats.getEnqueues().getCount());
128                        statsMessage.setLong("dequeueCount", stats.getDequeues().getCount());
129                        statsMessage.setLong("dispatchCount", stats.getDispatched().getCount());
130                        statsMessage.setLong("expiredCount", stats.getExpired().getCount());
131                        statsMessage.setLong("inflightCount", stats.getInflight().getCount());
132                        statsMessage.setLong("messagesCached", stats.getMessagesCached().getCount());
133                        // we are okay with the size without decimals so cast to long
134                        statsMessage.setLong("averageMessageSize", (long) stats.getMessageSize().getAverageSize());
135                        statsMessage.setInt("memoryPercentUsage", dest.getMemoryUsage().getPercentUsage());
136                        statsMessage.setLong("memoryUsage", dest.getMemoryUsage().getUsage());
137                        statsMessage.setLong("memoryLimit", dest.getMemoryUsage().getLimit());
138                        statsMessage.setDouble("averageEnqueueTime", stats.getProcessTime().getAverageTime());
139                        statsMessage.setDouble("maxEnqueueTime", stats.getProcessTime().getMaxTime());
140                        statsMessage.setDouble("minEnqueueTime", stats.getProcessTime().getMinTime());
141                        statsMessage.setLong("consumerCount", stats.getConsumers().getCount());
142                        statsMessage.setLong("producerCount", stats.getProducers().getCount());
143                        if (includeFirstMessageTimestamp) {
144                            if (dest instanceof Queue) {
145                                ((Queue) dest).doBrowse(tempFirstMessage, 1);
146                            }
147                            else if (dest instanceof Topic) {
148                                ((Topic) dest).doBrowse(tempFirstMessage, 1);
149                            }
150                            if (!tempFirstMessage.isEmpty()) {
151                                Message message = tempFirstMessage.get(0);
152                                // NOTICE: Client-side, you may get the broker "now" Timestamp by msg.getJMSTimestamp()
153                                // This allows for calculating age.
154                                statsMessage.setLong("firstMessageTimestamp", message.getBrokerInTime());
155                                tempFirstMessage.clear();
156                            }
157                        }
158                        statsMessage.setJMSCorrelationID(messageSend.getCorrelationId());
159                        sendStats(producerExchange.getConnectionContext(), statsMessage, replyTo);
160                    }
161                }
162                if(endListMessage){
163                    ActiveMQMapMessage statsMessage = new ActiveMQMapMessage();
164                    statsMessage.setJMSCorrelationID(messageSend.getCorrelationId());
165                    sendStats(producerExchange.getConnectionContext(),statsMessage,replyTo);
166                }
167
168            } else if (subStats) {
169                sendSubStats(producerExchange.getConnectionContext(), getBrokerView().getQueueSubscribers(), replyTo);
170                sendSubStats(producerExchange.getConnectionContext(), getBrokerView().getTopicSubscribers(), replyTo);
171            } else if (brokerStats) {
172
173                if (messageSend.getProperties().containsKey(STATS_BROKER_RESET_HEADER)) {
174                    getBrokerView().resetStatistics();
175                }
176
177                ActiveMQMapMessage statsMessage = new ActiveMQMapMessage();
178                SystemUsage systemUsage = brokerService.getSystemUsage();
179                DestinationStatistics stats = regionBroker.getDestinationStatistics();
180                statsMessage.setString("brokerName", regionBroker.getBrokerName());
181                statsMessage.setString("brokerId", regionBroker.getBrokerId().toString());
182                statsMessage.setLong("size", stats.getMessages().getCount());
183                statsMessage.setLong("enqueueCount", stats.getEnqueues().getCount());
184                statsMessage.setLong("dequeueCount", stats.getDequeues().getCount());
185                statsMessage.setLong("dispatchCount", stats.getDispatched().getCount());
186                statsMessage.setLong("expiredCount", stats.getExpired().getCount());
187                statsMessage.setLong("inflightCount", stats.getInflight().getCount());
188                // we are okay with the size without decimals so cast to long
189                statsMessage.setLong("averageMessageSize",(long) stats.getMessageSize().getAverageSize());
190                statsMessage.setLong("messagesCached", stats.getMessagesCached().getCount());
191                statsMessage.setInt("memoryPercentUsage", systemUsage.getMemoryUsage().getPercentUsage());
192                statsMessage.setLong("memoryUsage", systemUsage.getMemoryUsage().getUsage());
193                statsMessage.setLong("memoryLimit", systemUsage.getMemoryUsage().getLimit());
194                statsMessage.setInt("storePercentUsage", systemUsage.getStoreUsage().getPercentUsage());
195                statsMessage.setLong("storeUsage", systemUsage.getStoreUsage().getUsage());
196                statsMessage.setLong("storeLimit", systemUsage.getStoreUsage().getLimit());
197                statsMessage.setInt("tempPercentUsage", systemUsage.getTempUsage().getPercentUsage());
198                statsMessage.setLong("tempUsage", systemUsage.getTempUsage().getUsage());
199                statsMessage.setLong("tempLimit", systemUsage.getTempUsage().getLimit());
200                statsMessage.setDouble("averageEnqueueTime", stats.getProcessTime().getAverageTime());
201                statsMessage.setDouble("maxEnqueueTime", stats.getProcessTime().getMaxTime());
202                statsMessage.setDouble("minEnqueueTime", stats.getProcessTime().getMinTime());
203                statsMessage.setLong("consumerCount", stats.getConsumers().getCount());
204                statsMessage.setLong("producerCount", stats.getProducers().getCount());
205                String answer = brokerService.getTransportConnectorURIsAsMap().get("tcp");
206                answer = answer != null ? answer : "";
207                statsMessage.setString("openwire", answer);
208                answer = brokerService.getTransportConnectorURIsAsMap().get("stomp");
209                answer = answer != null ? answer : "";
210                statsMessage.setString("stomp", answer);
211                answer = brokerService.getTransportConnectorURIsAsMap().get("ssl");
212                answer = answer != null ? answer : "";
213                statsMessage.setString("ssl", answer);
214                answer = brokerService.getTransportConnectorURIsAsMap().get("stomp+ssl");
215                answer = answer != null ? answer : "";
216                statsMessage.setString("stomp+ssl", answer);
217                URI uri = brokerService.getVmConnectorURI();
218                answer = uri != null ? uri.toString() : "";
219                statsMessage.setString("vm", answer);
220                File file = brokerService.getDataDirectoryFile();
221                answer = file != null ? file.getCanonicalPath() : "";
222                statsMessage.setString("dataDirectory", answer);
223                statsMessage.setJMSCorrelationID(messageSend.getCorrelationId());
224                sendStats(producerExchange.getConnectionContext(), statsMessage, replyTo);
225            } else {
226                super.send(producerExchange, messageSend);
227            }
228        } else {
229            super.send(producerExchange, messageSend);
230        }
231    }
232
233    BrokerViewMBean getBrokerView() throws Exception {
234        if (this.brokerView == null) {
235            ObjectName brokerName = getBrokerService().getBrokerObjectName();
236            this.brokerView = (BrokerViewMBean) getBrokerService().getManagementContext().newProxyInstance(brokerName,
237                    BrokerViewMBean.class, true);
238        }
239        return this.brokerView;
240    }
241
242    @Override
243    public void start() throws Exception {
244        super.start();
245        LOG.info("Starting StatisticsBroker");
246    }
247
248    @Override
249    public void stop() throws Exception {
250        super.stop();
251    }
252
253    protected void sendSubStats(ConnectionContext context, ObjectName[] subscribers, ActiveMQDestination replyTo) throws Exception {
254        for (int i = 0; i < subscribers.length; i++) {
255            ObjectName name = subscribers[i];
256            SubscriptionViewMBean subscriber = (SubscriptionViewMBean)getBrokerService().getManagementContext().newProxyInstance(name, SubscriptionViewMBean.class, true);
257            ActiveMQMapMessage statsMessage = prepareSubscriptionMessage(subscriber);
258            sendStats(context, statsMessage, replyTo);
259        }
260    }
261
262    protected ActiveMQMapMessage prepareSubscriptionMessage(SubscriptionViewMBean subscriber) throws JMSException {
263        Broker regionBroker = getBrokerService().getRegionBroker();
264        ActiveMQMapMessage statsMessage = new ActiveMQMapMessage();
265        statsMessage.setString("brokerName", regionBroker.getBrokerName());
266        statsMessage.setString("brokerId", regionBroker.getBrokerId().toString());
267        statsMessage.setString("destinationName", subscriber.getDestinationName());
268        statsMessage.setString("clientId", subscriber.getClientId());
269        statsMessage.setString("connectionId", subscriber.getConnectionId());
270        statsMessage.setLong("sessionId", subscriber.getSessionId());
271        statsMessage.setString("selector", subscriber.getSelector());
272        statsMessage.setLong("enqueueCounter", subscriber.getEnqueueCounter());
273        statsMessage.setLong("dequeueCounter", subscriber.getDequeueCounter());
274        statsMessage.setLong("dispatchedCounter", subscriber.getDispatchedCounter());
275        statsMessage.setLong("dispatchedQueueSize", subscriber.getDispatchedQueueSize());
276        statsMessage.setInt("prefetchSize", subscriber.getPrefetchSize());
277        statsMessage.setInt("maximumPendingMessageLimit", subscriber.getMaximumPendingMessageLimit());
278        statsMessage.setBoolean("exclusive", subscriber.isExclusive());
279        statsMessage.setBoolean("retroactive", subscriber.isRetroactive());
280        statsMessage.setBoolean("slowConsumer", subscriber.isSlowConsumer());
281        return statsMessage;
282    }
283
284    protected void sendStats(ConnectionContext context, ActiveMQMapMessage msg, ActiveMQDestination replyTo)
285            throws Exception {
286        msg.setPersistent(false);
287        msg.setTimestamp(System.currentTimeMillis());
288        msg.setPriority((byte) javax.jms.Message.DEFAULT_PRIORITY);
289        msg.setType(AdvisorySupport.ADIVSORY_MESSAGE_TYPE);
290        msg.setMessageId(new MessageId(this.advisoryProducerId, this.messageIdGenerator.getNextSequenceId()));
291        msg.setDestination(replyTo);
292        msg.setResponseRequired(false);
293        msg.setProducerId(this.advisoryProducerId);
294        boolean originalFlowControl = context.isProducerFlowControl();
295        final ProducerBrokerExchange producerExchange = new ProducerBrokerExchange();
296        producerExchange.setConnectionContext(context);
297        producerExchange.setMutable(true);
298        producerExchange.setProducerState(new ProducerState(new ProducerInfo()));
299        try {
300            context.setProducerFlowControl(false);
301            this.next.send(producerExchange, msg);
302        } finally {
303            context.setProducerFlowControl(originalFlowControl);
304        }
305    }
306
307}