// 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.apache.tapestry5.services;

import org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static java.lang.String.format;
import static java.lang.System.out;

/**
 * Tests single-thread synchronization overhead using different techniques. Note that we're fudging things a bit by
 * getting a read lock for a write operation .... it's just that I'm more concerned about read locks (which will be very
 * common) than about write locks (very rare). Another concern is that hotspot is going to mess up our synchronization
 * when it see we're not really doing anything multi-threaded.
 *
 * The results show that using the {@link org.apache.tapestry5.internal.annotations.Concurrent} aspect (which used a
 * {@link java.util.concurrent.locks.ReentrantReadWriteLock} under the covers) is about 4x as expensive as just using
 * the synchronized keyword. There are some anomolous results ... for example, ReadWriteLockRunner is consistently
 * slower than ReadWriteLockAspectRunner (one would expect it to be the other way around ... must be something about how
 * AspectJ weaves the code ... and it's use of static methods in many cases).
 *
 * Well, the Concurrent aspect is gone, replaced with the {@link ConcurrentBarrier} utility.
 */
public class SyncCostBench
{
    /**
     * Calculates a fibunacci series.
     */
    static class Worker implements Runnable
    {
        private long[] series = { 1, 1 };

        public void run()
        {
            long value = series[0] + series[1];

            // Now shift the values down to prepare for the next iteration.

            series[0] = series[1];
            series[1] = value;
        }
    }

    static class SimpleRunner implements Runnable
    {
        private final Runnable delegate;

        public SimpleRunner(Runnable delegate)
        {
            this.delegate = delegate;
        }

        public void run()
        {
            delegate.run();
        }
    }

    static class SynchronizedRunner implements Runnable
    {
        private final Runnable delegate;

        public SynchronizedRunner(Runnable delegate)
        {
            this.delegate = delegate;
        }

        public synchronized void run()
        {
            delegate.run();
        }
    }

    static class ReadWriteLockAspectRunner implements Runnable
    {
        private final ConcurrentBarrier barrier = new ConcurrentBarrier();

        private final Runnable delegate;

        public ReadWriteLockAspectRunner(Runnable delegate)
        {
            this.delegate = delegate;
        }

        public void run()
        {
            barrier.withRead(delegate);
        }
    }

    static class ReadWriteLockRunner implements Runnable
    {
        private final Runnable delegate;

        private final ReadWriteLock lock = new ReentrantReadWriteLock();

        public ReadWriteLockRunner(Runnable delegate)
        {
            this.delegate = delegate;
        }

        public void run()
        {

            try
            {
                lock.readLock().lock();

                delegate.run();
            }
            finally
            {
                lock.readLock().unlock();
            }

        }
    }

    private static final int WARMUP_BLOCK_SIZE = 1000;

    private static final int BLOCK_SIZE = 5 * 1000 * 1000;

    static class BlockRunner implements Runnable
    {
        private final Runnable delegate;

        private final int blockSize;

        public BlockRunner(int blockSize, Runnable delegate)
        {
            this.blockSize = blockSize;
            this.delegate = delegate;
        }

        public void run()
        {
            for (int i = 0; i < blockSize; i++)
                delegate.run();
        }
    }

    public static void main(String[] args) throws Exception
    {
        Runnable simple = new SimpleRunner(new Worker());
        Runnable synched = new SynchronizedRunner(new Worker());
        Runnable rw = new ReadWriteLockRunner(new Worker());
        Runnable aspect = new ReadWriteLockAspectRunner(new Worker());

        out.println(format("%40s %9s %9s %9s", ",simple", ",synched", ",rw", ",aspect"));

        stage("warmup");

        go(WARMUP_BLOCK_SIZE, simple);
        go(WARMUP_BLOCK_SIZE, synched);
        go(WARMUP_BLOCK_SIZE, rw);
        go(WARMUP_BLOCK_SIZE, aspect);

        out.println();

        for (int i = 0; i < 10; i++)
        {
            Thread.sleep(5 * 1000);
            System.gc();

            stage(format("stage #%d", i + 1));
            go(BLOCK_SIZE, simple);
            go(BLOCK_SIZE, synched);
            go(BLOCK_SIZE, rw);
            go(BLOCK_SIZE, aspect);

            out.println();
        }
    }

    private static void stage(String name)
    {
        out.print(format("%30s", name));
    }

    private static void go(int blockSize, Runnable runner) throws InterruptedException
    {

        Thread t = new Thread(new BlockRunner(blockSize, runner));

        long tick = System.nanoTime();

        t.start();

        // Now wait for it to finish.

        t.join();

        long tock = System.nanoTime();

        out.print(format(",%9d", tock - tick));
    }
}
