/**
* Copyright 2015 Otto (GmbH & Co KG)
*
* 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.ottogroup.bi.spqr.pipeline.component.queue.chronicle;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.junit.Test;
import com.ottogroup.bi.spqr.exception.RequiredInputMissingException;
import com.ottogroup.bi.spqr.pipeline.message.StreamingDataMessage;
import com.ottogroup.bi.spqr.pipeline.queue.StreamingMessageQueueConsumer;
import com.ottogroup.bi.spqr.pipeline.queue.StreamingMessageQueueProducer;
import com.ottogroup.bi.spqr.pipeline.queue.chronicle.DefaultStreamingMessageQueue;
import net.openhft.chronicle.Chronicle;
/**
* Test case for {@link DefaultStreamingMessageQueue}
* @author mnxfst
* @since Mar 5, 2015
*/
public class DefaultStreamingMessageQueueTest {
private static final Logger logger = Logger.getLogger(DefaultStreamingMessageQueueTest.class);
private static final int numberOfMessagesPerfTest = 100;
/**
* Test case for {@link DefaultStreamingMessageQueue#initialize(java.util.Properties)} being provided
* null where an exception is the expected behavior
*/
@Test
public void testInitialize_withNullProperties() {
try {
new DefaultStreamingMessageQueue().initialize(null);
Assert.fail("Missing required properties");
} catch(RequiredInputMissingException e) {
// expected
}
}
/**
* Test case for {@link DefaultStreamingMessageQueue#initialize(java.util.Properties)} being provided
* an empty properties set where an exception is the expected behavior
*/
@Test
public void testInitialize_withEmptyProperties() throws RequiredInputMissingException {
DefaultStreamingMessageQueue queue = new DefaultStreamingMessageQueue();
queue.setId("testInitialize_withEmptyProperties");
queue.initialize(new Properties());
}
/**
* Test case for {@link DefaultStreamingMessageQueue#initialize(java.util.Properties)} being provided
* a properties set where the id is missing which must lead to a runtime exception
*/
@Test
public void testInitialize_withEmptyIdentifier() {
try {
Properties props = new Properties();
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_DELETE_ON_EXIT, "true");
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_PATH, System.getProperty("java.io.tmpdir"));
new DefaultStreamingMessageQueue().initialize(props);
Assert.fail("Missing required properties");
} catch(RequiredInputMissingException e) {
// expected
}
}
/**
* Test case for {@link DefaultStreamingMessageQueue#initialize(java.util.Properties)} being provided
* a properties set where the queue path is missing which must lead to a runtime exception
*/
@Test
public void testInitialize_withEmptyPath() throws RequiredInputMissingException {
Properties props = new Properties();
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_DELETE_ON_EXIT, "true");
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_PATH, "");
DefaultStreamingMessageQueue queue = new DefaultStreamingMessageQueue();
queue.setId("testInitialize_withEmptyPath");
queue.initialize(props);
}
/**
* Test case for {@link DefaultStreamingMessageQueue#initialize(java.util.Properties)} being provided
* a properties set where all required settings are contained - no exception is expected
*/
@Test
public void testInitialize_withValidSettings() throws RequiredInputMissingException {
Properties props = new Properties();
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_DELETE_ON_EXIT, "true");
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_PATH, System.getProperty("java.io.tmpdir"));
DefaultStreamingMessageQueue queue = new DefaultStreamingMessageQueue();
queue.setId("testInitialize_withValidSettings");
queue.initialize(props);
}
/**
* Test case for {@link DefaultStreamingMessageQueue#next()} requesting a single message from a
* temporary queue which must be returned without errors
*/
@Test
public void testNext_withSingleMessage() throws IOException, RequiredInputMissingException {
Properties props = new Properties();
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_DELETE_ON_EXIT, "true");
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_PATH, System.getProperty("java.io.tmpdir"));
DefaultStreamingMessageQueue inbox = new DefaultStreamingMessageQueue();
inbox.setId("testNext_withSingleMessages");
inbox.initialize(props);
long timestamp = System.currentTimeMillis();
String text = "This is a simple test message";
byte[] content = text.getBytes();
Assert.assertTrue(inbox.getProducer().insert(new StreamingDataMessage(content, timestamp)));
StreamingDataMessage msg = inbox.getConsumer().next();
Assert.assertTrue("Values must be equal", StringUtils.equalsIgnoreCase(new String(content), new String(msg.getBody())));
Assert.assertEquals("Values must be equal", timestamp, msg.getTimestamp());
}
/**
* Inserts a configurable number of messages into a {@link Chronicle} and measures the
* duration it takes to read the content from it using the {@link DefaultStreamingMessageQueue} implementation
*/
// @Test
public void testNext_performanceTest() throws Exception {
Properties props = new Properties();
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_DELETE_ON_EXIT, "true");
props.put(DefaultStreamingMessageQueue.CFG_CHRONICLE_QUEUE_PATH, System.getProperty("java.io.tmpdir"));
final DefaultStreamingMessageQueue inbox = new DefaultStreamingMessageQueue();
inbox.setId("testNext_performanceTest");
inbox.initialize(props);
final StreamingMessageQueueProducer producer = inbox.getProducer();
final StreamingMessageQueueConsumer consumer = inbox.getConsumer();
final CountDownLatch latch = new CountDownLatch(numberOfMessagesPerfTest);
ExecutorService svc = Executors.newCachedThreadPool();
Future<Integer> producerDurationFuture = svc.submit(new Callable<Integer>() {
public Integer call() {
StreamingDataMessage object = new StreamingDataMessage(new byte[]{01,2,3,4,5,6,7,9}, System.currentTimeMillis());
long s1 = System.nanoTime();
for(int i = 0; i < numberOfMessagesPerfTest; i++) {
producer.insert(object);
}
long s2 = System.nanoTime();
return (int)(s2-s1);
}
});
Future<Integer> durationFuture = svc.submit(new Callable<Integer>() {
public Integer call() {
StreamingDataMessage msg = null;
long start = System.nanoTime();
while(true) {
msg = consumer.next();
if(msg != null) {
latch.countDown();
if(latch.getCount() == 0)
break;
} else {
LockSupport.parkNanos(1);
}
}
long end = System.nanoTime();
return (int)(end-start);
}
});
try {
Assert.assertTrue("Failed to receive expected number of messages", latch.await(10, TimeUnit.SECONDS));
} catch (InterruptedException e) {
Assert.fail("Failed to receive expected number of messages");
}
int producerDuration = producerDurationFuture.get();
int duration = durationFuture.get();
double messagesPerNano = ((double)numberOfMessagesPerfTest / (double)duration);
double messagesPerNanoRounded = (double)Math.round(messagesPerNano* 10000) / 10000;
double messagesPerMilli = messagesPerNano * 1000000;
messagesPerMilli = (double)Math.round(messagesPerMilli * 100) / 100;
long messagesPerSecondTmps = Math.round(messagesPerNano * 1000000 * 1000);
double messagesPerSecond = (double)Math.round(messagesPerSecondTmps);;
double nanosPerMessage = ((double)duration / (double)numberOfMessagesPerfTest);
nanosPerMessage = (double)Math.round(nanosPerMessage * 100) /100;
logger.info("message count: " + numberOfMessagesPerfTest);
logger.info("message producing: " + producerDuration +"ns, "+TimeUnit.NANOSECONDS.toMillis(producerDuration)+"ms, "+TimeUnit.NANOSECONDS.toSeconds(producerDuration)+"s");
logger.info("message consumption: " + duration +"ns, "+TimeUnit.NANOSECONDS.toMillis(duration)+"ms, "+TimeUnit.NANOSECONDS.toSeconds(duration)+"s");
logger.info("message throughput: " + messagesPerNanoRounded + " msgs/ns, "+ messagesPerMilli + " msgs/ms, " + messagesPerSecond + " msgs/s");
svc.shutdownNow();
}
}