/** 
 * 
 * Copyright 2004 Hiram Chirino
 * 
 * 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 org.activemq.store.journal;

import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;

import junit.framework.TestCase;

import org.activemq.ActiveMQConnectionFactory;
import org.activemq.broker.impl.BrokerContainerImpl;
import org.activemq.message.ActiveMQQueue;

import EDU.oswego.cs.dl.util.concurrent.Callable;
import EDU.oswego.cs.dl.util.concurrent.Latch;
import EDU.oswego.cs.dl.util.concurrent.Semaphore;

/**
 * Benchmarks the Journal Store by using an embeded broker to add and remove messages from 
 * the store.
 * 
 * Make sure you run with jvm option -server (makes a big difference).
 * The tests simulate storing 1000 1k jms messages to see the rate of 
 * processing msg/sec.
 * 
 * @version $Revision: 1.1 $
 */
public class JournalBrokerBenchmark extends TestCase {

    private static final int MESSAGE_COUNT = Integer.parseInt(System.getProperty("MESSAGE_COUNT","10000"));
    private BrokerContainerImpl broker;
    private ActiveMQQueue dest;
    private ActiveMQConnectionFactory connectionFactory;

    public static void main(String[] args) {
        junit.textui.TestRunner.run(JournalBrokerBenchmark.class);
    }
    
    protected void setUp() throws Exception {
        
        broker = new BrokerContainerImpl("localhost");
        broker.addConnector("tcp://localhost:61616");
        broker.start();
        connectionFactory = new ActiveMQConnectionFactory(broker, "tcp://localhost:61616");
//        connectionFactory.setTurboBoost(true);
//        connectionFactory.setCachingEnabled(false);
//        connectionFactory.setCopyMessageOnSend(true);
        dest = new ActiveMQQueue("TEST");
    }

    protected void tearDown() throws Exception {
        broker.stop();
    }

    static class ProgressPrinter {
        private final int total;
        private final int interval;
        int percentDone=0;
        int counter=0;
        
        public ProgressPrinter(int total, int interval) {
            this.total=total;
            this.interval = interval;
        }
        
        synchronized public void increment() {
        	update(++counter);
        }
        
        synchronized public void update(int current) {
            int at = 100*current/total;
            if( (percentDone/interval) != (at/interval) ) {
                percentDone=at;
                System.out.println("Completed: "+percentDone+"%");
            }            
        }
    }
    
    /**
     * Moves about 6000 msg/sec on a Win XP 3.2 ghz Intel Machine.
     * 
     * @throws Throwable  
     */
    public void testConcurrentSendReceive() throws Throwable {
        
        final int PRODUCER_COUNT=Integer.parseInt(System.getProperty("PRODUCER_COUNT","10"));
        final int CONSUMER_COUNT=Integer.parseInt(System.getProperty("CONSUMER_COUNT","10"));
        
        final ProgressPrinter pp = new ProgressPrinter(MESSAGE_COUNT*2, 5);

        final Semaphore connectionsEstablished = new Semaphore(1-(CONSUMER_COUNT+PRODUCER_COUNT)); 
        final Latch startTest = new Latch(); 
        final Semaphore testsFinished = new Semaphore(1-(CONSUMER_COUNT+PRODUCER_COUNT)); 
        
        final Callable producer = new Callable() {
            public Object call() throws JMSException, InterruptedException {
                Connection connection = connectionFactory.createConnection();
                Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                MessageProducer producer = session.createProducer(dest);
                producer.setDeliveryMode(DeliveryMode.PERSISTENT);
                BytesMessage message = session.createBytesMessage();
                message.writeBytes(new byte[1024]);
                connection.start();
                
                connectionsEstablished.release();
                
                startTest.acquire();                
                final int msgs = (MESSAGE_COUNT/PRODUCER_COUNT)+1;
                for (int i = 0; i < msgs; i++) {
                    pp.increment();
                    producer.send(message);
                }                
                
                testsFinished.release();                
                connection.close();
                return null;
            }
        };
        
        final Callable consumer = new Callable() {
            public Object call() throws JMSException, InterruptedException {
                final Latch doneLatch = new Latch();                
                Connection connection = connectionFactory.createConnection();                
                Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                MessageConsumer consumer = session.createConsumer(dest);

                connectionsEstablished.release();
                startTest.acquire();

                final int msgs = (MESSAGE_COUNT/CONSUMER_COUNT)-1;
                consumer.setMessageListener(new MessageListener(){
                    int counter=0;
                    public void onMessage(Message msg) {                
                        pp.increment();
                        counter++;
                        if( counter >= msgs ) {
                            doneLatch.release();
                        }
                    }
                });
                connection.start();
                doneLatch.acquire();

                testsFinished.release();                
                connection.close();
                return null;
            }
        };
        
        final Throwable workerError[] = new Throwable[1];
        for( int i=0; i < PRODUCER_COUNT; i++ ) {
            new Thread("Producer:"+i) {
                public void run() {
                    try {
                        producer.call();
                    } catch (Throwable e) {
                        e.printStackTrace();
                        workerError[0] = e;
                    }
                }
            }.start();
        }

        for( int i=0; i < CONSUMER_COUNT; i++ ) {
            new Thread("Consumer:"+i) {
                public void run() {
                    try {
                        consumer.call();
                    } catch (Throwable e) {
                        workerError[0] = e;
                    }
                }
            }.start();
        }

        connectionsEstablished.acquire();

//        System.out.println("ready.");
//        System.in.read();System.in.read();
        
        startTest.release();
        long start = System.currentTimeMillis();
        testsFinished.acquire();
        long end = System.currentTimeMillis();
        System.out.println(getName() + ": test duration: " + (end - start) + " ms, published+acked msg/s: "
                + (MESSAGE_COUNT * 1000f / (end - start)));
        
//        System.out.println("ready.");
//        System.in.read();System.in.read();

        if( workerError[0] != null )
            throw workerError[0];

    }

}
