/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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
*
* 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 com.hazelcast.topic;
import com.hazelcast.config.Config;
import com.hazelcast.config.TopicConfig;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.ITopic;
import com.hazelcast.core.Message;
import com.hazelcast.core.MessageListener;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.HazelcastSerialClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.annotation.NightlyTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertEquals;
/**
* This test creates a cluster of HazelcastInstances and a bunch of Topics.
* <p/>
* On each instance, there is a listener for each topic.
* <p/>
* There is a bunch of threads, that selects a random instance with a random topic to publish a message (int) on.
* <p/>
* To verify that everything is fine, we expect that the total messages send by each topic is the same as the total
* sum of messages receives by the topic listeners.
*/
@RunWith(HazelcastSerialClassRunner.class)
@Category(NightlyTest.class)
public class TopicStressTest extends HazelcastTestSupport {
public static final int PUBLISH_THREAD_COUNT = 10;
public static final int NODE_COUNT = 10;
public static final int TOPIC_COUNT = 10;
public static final int RUNNING_TIME_SECONDS = 600;
// if we set this value very low, it could be that events are dropped due to overload of the event queue
public static final int MAX_PUBLISH_DELAY_MILLIS = 25;
private HazelcastInstance[] instances;
private CountDownLatch startLatch;
private PublishThread[] publishThreads;
private Map<String, List<MessageListenerImpl>> listenerMap;
@Parameterized.Parameter
public boolean multiThreadingEnabled;
@Before
public void setUp() {
TopicConfig topicConfig = new TopicConfig();
topicConfig.setName("topic*");
topicConfig.setMultiThreadingEnabled(multiThreadingEnabled);
Config config = new Config();
config.addTopicConfig(topicConfig);
instances = createHazelcastInstanceFactory(NODE_COUNT).newInstances(config);
startLatch = new CountDownLatch(1);
publishThreads = new PublishThread[PUBLISH_THREAD_COUNT];
for (int threadIndex = 0; threadIndex < publishThreads.length; threadIndex++) {
PublishThread publishThread = new PublishThread(startLatch);
publishThread.start();
publishThreads[threadIndex] = publishThread;
}
listenerMap = new HashMap<String, List<MessageListenerImpl>>();
for (int topicIndex = 0; topicIndex < TOPIC_COUNT; topicIndex++) {
String topicName = getTopicName(topicIndex);
List<MessageListenerImpl> listeners = registerTopicListeners(topicName, instances);
listenerMap.put(topicName, listeners);
}
}
@Test(timeout = RUNNING_TIME_SECONDS * 2 * 1000)
public void test() throws Exception {
startLatch.countDown();
System.out.printf("Test is going to run for %s seconds\n", RUNNING_TIME_SECONDS);
for (PublishThread thread : publishThreads) {
thread.join();
}
System.out.println("All publish threads have completed");
assertTrueEventually(new AssertTask() {
@Override
public void run() {
for (int topicIndex = 0; topicIndex < TOPIC_COUNT; topicIndex++) {
String topicName = getTopicName(topicIndex);
long expected = getExpectedCount(topicName);
long actual = getActualCount(topicName);
assertEquals("Count for topic " + topicName + " is not the same", expected, actual);
}
}
});
}
private long getExpectedCount(String topic) {
long result = 0;
for (PublishThread publishThread : publishThreads) {
Long count = publishThread.messageCount.get(topic);
if (count != null) {
result += count;
}
}
// since each message is send to multiple nodes, we need to multiply it
return result * NODE_COUNT;
}
private long getActualCount(String topic) {
long result = 0;
List<MessageListenerImpl> listeners = listenerMap.get(topic);
if (listeners == null) {
return 0;
}
for (MessageListenerImpl listener : listeners) {
result += listener.counter.get();
}
return result;
}
private String getTopicName(int topicIndex) {
return "topic" + topicIndex;
}
private class PublishThread extends Thread {
private final Random random = new Random();
private final Map<String, Long> messageCount = new HashMap<String, Long>();
private final CountDownLatch startLatch;
private PublishThread(CountDownLatch startLatch) {
this.startLatch = startLatch;
}
@Override
public void run() {
try {
startLatch.await();
long endTimeMillis = getEndTimeMillis();
while (System.currentTimeMillis() < endTimeMillis) {
HazelcastInstance instance = randomInstance();
ITopic<Integer> topic = randomTopic(instance);
int inc = random.nextInt(100);
topic.publish(inc);
updateCount(topic, inc);
randomSleep();
}
} catch (Throwable t) {
t.printStackTrace();
}
}
private long getEndTimeMillis() {
return System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(RUNNING_TIME_SECONDS);
}
private void updateCount(ITopic<Integer> topic, int inc) {
String topicName = topic.getName();
Long count = messageCount.get(topicName);
if (count == null) {
count = 0L;
}
count += inc;
messageCount.put(topicName, count);
}
private void randomSleep() {
try {
Thread.sleep(random.nextInt(MAX_PUBLISH_DELAY_MILLIS));
} catch (InterruptedException ignored) {
}
}
private ITopic<Integer> randomTopic(HazelcastInstance instance) {
String randomTopicName = getTopicName(random.nextInt(TOPIC_COUNT));
return instance.getTopic(randomTopicName);
}
private HazelcastInstance randomInstance() {
int index = random.nextInt(instances.length);
return instances[index];
}
}
private List<MessageListenerImpl> registerTopicListeners(String topicName, HazelcastInstance[] instances) {
List<MessageListenerImpl> listeners = new LinkedList<MessageListenerImpl>();
for (HazelcastInstance hz : instances) {
MessageListenerImpl listener = new MessageListenerImpl();
ITopic<Integer> topic = hz.getTopic(topicName);
topic.addMessageListener(listener);
listeners.add(listener);
}
return listeners;
}
private class MessageListenerImpl implements MessageListener<Integer> {
private final AtomicLong counter = new AtomicLong();
@Override
public void onMessage(Message<Integer> message) {
int inc = message.getMessageObject();
counter.addAndGet(inc);
}
}
}