/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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.apache.activemq.broker.region.cursors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.atomic.AtomicLong; import javax.jms.Connection; import javax.jms.DeliveryMode; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TopicSession; import javax.jms.TopicSubscriber; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.region.DurableTopicSubscription; import org.apache.activemq.broker.region.Topic; import org.apache.activemq.command.ActiveMQTextMessage; import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.store.MessageStoreSubscriptionStatistics; import org.apache.activemq.store.TopicMessageStore; import org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter; import org.apache.activemq.util.SubscriptionKey; import org.apache.activemq.util.Wait; import org.apache.activemq.util.Wait.Condition; import org.apache.commons.io.FileUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This test checks that pending message metrics work properly with KahaDB * * AMQ-5923, AMQ-6375 * */ @RunWith(Parameterized.class) public class KahaDBPendingMessageCursorTest extends AbstractPendingMessageCursorTest { protected static final Logger LOG = LoggerFactory .getLogger(KahaDBPendingMessageCursorTest.class); @Parameters(name = "prioritizedMessages={0},enableSubscriptionStatistics={1}") public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { // use priority messages { true, true }, { true, false }, // don't use priority messages { false, true }, { false, false } }); } @Rule public TemporaryFolder dataFileDir = new TemporaryFolder(new File("target")); /** * @param prioritizedMessages */ public KahaDBPendingMessageCursorTest(final boolean prioritizedMessages, final boolean enableSubscriptionStatistics) { super(prioritizedMessages); this.enableSubscriptionStatistics = enableSubscriptionStatistics; } @Override protected void setUpBroker(boolean clearDataDir) throws Exception { if (clearDataDir && dataFileDir.getRoot().exists()) FileUtils.cleanDirectory(dataFileDir.getRoot()); super.setUpBroker(clearDataDir); } @Override protected void initPersistence(BrokerService brokerService) throws IOException { broker.setPersistent(true); KahaDBPersistenceAdapter persistenceAdapter = new KahaDBPersistenceAdapter(); persistenceAdapter.setDirectory(dataFileDir.getRoot()); persistenceAdapter.setEnableSubscriptionStatistics(enableSubscriptionStatistics); broker.setPersistenceAdapter(persistenceAdapter); } /** * Test that the the counter restores size and works after restart and more * messages are published * * @throws Exception */ @Test public void testDurableMessageSizeAfterRestartAndPublish() throws Exception { AtomicLong publishedMessageSize = new AtomicLong(); Connection connection = new ActiveMQConnectionFactory(brokerConnectURI).createConnection(); connection.setClientID("clientId"); connection.start(); Topic topic = publishTestMessagesDurable(connection, new String[] {"sub1"}, 200, publishedMessageSize, DeliveryMode.PERSISTENT); SubscriptionKey subKey = new SubscriptionKey("clientId", "sub1"); // verify the count and size verifyPendingStats(topic, subKey, 200, publishedMessageSize.get()); verifyStoreStats(topic, 200, publishedMessageSize.get()); //should be equal in this case long beforeRestartSize = topic.getDurableTopicSubs().get(subKey).getPendingMessageSize(); assertEquals(beforeRestartSize, topic.getMessageStore().getMessageStoreStatistics().getMessageSize().getTotalSize()); // stop, restart broker and publish more messages stopBroker(); this.setUpBroker(false); //verify that after restart the size is the same as before restart on recovery topic = (Topic) getBroker().getDestination(new ActiveMQTopic(defaultTopicName)); assertEquals(beforeRestartSize, topic.getDurableTopicSubs().get(subKey).getPendingMessageSize()); connection = new ActiveMQConnectionFactory(brokerConnectURI).createConnection(); connection.setClientID("clientId"); connection.start(); topic = publishTestMessagesDurable(connection, new String[] {"sub1"}, 200, publishedMessageSize, DeliveryMode.PERSISTENT); // verify the count and size verifyPendingStats(topic, subKey, 400, publishedMessageSize.get()); verifyStoreStats(topic, 400, publishedMessageSize.get()); } @Test public void testMessageSizeTwoDurablesPartialConsumption() throws Exception { AtomicLong publishedMessageSize = new AtomicLong(); Connection connection = new ActiveMQConnectionFactory(brokerConnectURI).createConnection(); connection.setClientID("clientId"); connection.start(); SubscriptionKey subKey = new SubscriptionKey("clientId", "sub1"); SubscriptionKey subKey2 = new SubscriptionKey("clientId", "sub2"); org.apache.activemq.broker.region.Topic dest = publishTestMessagesDurable( connection, new String[] {"sub1", "sub2"}, 200, publishedMessageSize, DeliveryMode.PERSISTENT); //verify the count and size - durable is offline so all 200 should be pending since none are in prefetch verifyPendingStats(dest, subKey, 200, publishedMessageSize.get()); verifyStoreStats(dest, 200, publishedMessageSize.get()); //consume all messages consumeDurableTestMessages(connection, "sub1", 50, publishedMessageSize); //150 should be left verifyPendingStats(dest, subKey, 150, publishedMessageSize.get()); //200 should be left verifyPendingStats(dest, subKey2, 200, publishedMessageSize.get()); verifyStoreStats(dest, 200, publishedMessageSize.get()); connection.close(); } /** * Test that the the counter restores size and works after restart and more * messages are published * * @throws Exception */ @Test public void testNonPersistentDurableMessageSize() throws Exception { AtomicLong publishedMessageSize = new AtomicLong(); Connection connection = new ActiveMQConnectionFactory(brokerConnectURI).createConnection(); connection.setClientID("clientId"); connection.start(); Topic topic = publishTestMessagesDurable(connection, new String[] {"sub1"}, 200, publishedMessageSize, DeliveryMode.NON_PERSISTENT); SubscriptionKey subKey = new SubscriptionKey("clientId", "sub1"); // verify the count and size verifyPendingStats(topic, subKey, 200, publishedMessageSize.get()); verifyStoreStats(topic, 0, 0); } /** * Test that the subscription counters are properly set when enabled * and not set when disabled * * @throws Exception */ @Test public void testEnabledSubscriptionStatistics() throws Exception { AtomicLong publishedMessageSize = new AtomicLong(); Connection connection = new ActiveMQConnectionFactory(brokerConnectURI).createConnection(); connection.setClientID("clientId"); connection.start(); SubscriptionKey subKey = new SubscriptionKey("clientId", "sub1"); SubscriptionKey subKey2 = new SubscriptionKey("clientId", "sub2"); org.apache.activemq.broker.region.Topic dest = publishTestMessagesDurable( connection, new String[] {"sub1", "sub2"}, 200, publishedMessageSize, DeliveryMode.PERSISTENT); TopicMessageStore store = (TopicMessageStore) dest.getMessageStore(); MessageStoreSubscriptionStatistics stats = store.getMessageStoreSubStatistics(); if (enableSubscriptionStatistics) { assertTrue(stats.getMessageCount(subKey.toString()).getCount() == 200); assertTrue(stats.getMessageSize(subKey.toString()).getTotalSize() > 0); assertTrue(stats.getMessageCount(subKey2.toString()).getCount() == 200); assertTrue(stats.getMessageSize(subKey2.toString()).getTotalSize() > 0); assertEquals(stats.getMessageCount().getCount(), stats.getMessageCount(subKey.toString()).getCount() + stats.getMessageSize(subKey.toString()).getCount()); assertEquals(stats.getMessageSize().getTotalSize(), stats.getMessageSize(subKey.toString()).getTotalSize() + stats.getMessageSize(subKey2.toString()).getTotalSize()); //Delete second subscription and verify stats are updated accordingly store.deleteSubscription(subKey2.getClientId(), subKey2.getSubscriptionName()); assertEquals(stats.getMessageCount().getCount(), stats.getMessageCount(subKey.toString()).getCount()); assertEquals(stats.getMessageSize().getTotalSize(), stats.getMessageSize(subKey.toString()).getTotalSize()); assertTrue(stats.getMessageCount(subKey2.toString()).getCount() == 0); assertTrue(stats.getMessageSize(subKey2.toString()).getTotalSize() == 0); } else { assertTrue(stats.getMessageCount(subKey.toString()).getCount() == 0); assertTrue(stats.getMessageSize(subKey.toString()).getTotalSize() == 0); assertTrue(stats.getMessageCount(subKey2.toString()).getCount() == 0); assertTrue(stats.getMessageSize(subKey2.toString()).getTotalSize() == 0); assertEquals(0, stats.getMessageCount().getCount()); assertEquals(0, stats.getMessageSize().getTotalSize()); } } @Test public void testUpdateMessageSubSize() throws Exception { Connection connection = new ActiveMQConnectionFactory(brokerConnectURI).createConnection(); connection.setClientID("clientId"); connection.start(); Session session = connection.createSession(false, TopicSession.AUTO_ACKNOWLEDGE); javax.jms.Topic dest = session.createTopic(defaultTopicName); session.createDurableSubscriber(dest, "sub1"); session.createDurableSubscriber(dest, "sub2"); MessageProducer prod = session.createProducer(dest); ActiveMQTextMessage message = new ActiveMQTextMessage(); message.setText("SmallMessage"); prod.send(message); SubscriptionKey subKey = new SubscriptionKey("clientId", "sub1"); SubscriptionKey subKey2 = new SubscriptionKey("clientId", "sub1"); final Topic topic = (Topic) getBroker().getDestination(new ActiveMQTopic(defaultTopicName)); final DurableTopicSubscription sub = topic.getDurableTopicSubs().get(subKey); final DurableTopicSubscription sub2 = topic.getDurableTopicSubs().get(subKey2); long sizeBeforeUpdate = sub.getPendingMessageSize(); message = (ActiveMQTextMessage) topic.getMessageStore().getMessage(message.getMessageId()); message.setText("LargerMessageLargerMessage"); //update the message topic.getMessageStore().updateMessage(message); //should be at least 10 bytes bigger and match the store size assertTrue(sub.getPendingMessageSize() > sizeBeforeUpdate + 10); assertEquals(sub.getPendingMessageSize(), topic.getMessageStore().getMessageSize()); assertEquals(sub.getPendingMessageSize(), sub2.getPendingMessageSize()); } @Test public void testUpdateMessageSubSizeAfterConsume() throws Exception { Connection connection = new ActiveMQConnectionFactory(brokerConnectURI).createConnection(); connection.setClientID("clientId"); connection.start(); Session session = connection.createSession(false, TopicSession.AUTO_ACKNOWLEDGE); javax.jms.Topic dest = session.createTopic(defaultTopicName); session.createDurableSubscriber(dest, "sub1"); TopicSubscriber subscriber2 = session.createDurableSubscriber(dest, "sub2"); MessageProducer prod = session.createProducer(dest); ActiveMQTextMessage message = new ActiveMQTextMessage(); message.setText("SmallMessage"); ActiveMQTextMessage message2 = new ActiveMQTextMessage(); message2.setText("SmallMessage2"); prod.send(message); prod.send(message2); //Receive first message for sub 2 and wait for stats to update subscriber2.receive(); SubscriptionKey subKey = new SubscriptionKey("clientId", "sub1"); SubscriptionKey subKey2 = new SubscriptionKey("clientId", "sub2"); final Topic topic = (Topic) getBroker().getDestination(new ActiveMQTopic(defaultTopicName)); final DurableTopicSubscription sub = topic.getDurableTopicSubs().get(subKey); final DurableTopicSubscription sub2 = topic.getDurableTopicSubs().get(subKey2); Wait.waitFor(new Condition() { @Override public boolean isSatisified() throws Exception { return sub.getPendingMessageSize() > sub2.getPendingMessageSize(); } }); long sizeBeforeUpdate = sub.getPendingMessageSize(); long sizeBeforeUpdate2 = sub2.getPendingMessageSize(); //update message 2 message = (ActiveMQTextMessage) topic.getMessageStore().getMessage(message.getMessageId()); message.setText("LargerMessageLargerMessage"); //update the message topic.getMessageStore().updateMessage(message); //should be at least 10 bytes bigger and match the store size assertTrue(sub.getPendingMessageSize() > sizeBeforeUpdate + 10); assertEquals(sub.getPendingMessageSize(), topic.getMessageStore().getMessageSize()); //Sub2 only has 1 message so should be less than sub, verify that the update message //didn't update the stats of sub2 and sub1 should be over twice as large since the //updated message is bigger assertTrue(sub.getPendingMessageSize() > 2 * sub2.getPendingMessageSize()); assertEquals(sizeBeforeUpdate2, sub2.getPendingMessageSize()); } }