/** * 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(); } }