/* Glazed Lists                                                      (c) 2005 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
/*                                                          StarLight Systems */
package ca.odell.glazedlists.util.concurrent;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;

import java.util.Random;


/**
 * This application tests thread contention issues with GlazedLists. It's intended to be
 * used for benchmarking the cores locks rather than being an exhaustive test of each
 * list.
 *
 * @author <a href="mailto:rob@starlight-systems.com">Rob Eden</a>
 */
public class ThreadContentionPerformance {
	public static void main(String[] args) {
		if (args.length != 1 && args.length != 6) {
			printUsage();
			return;
		}

		int writer_threads;
		long writer_min_pause_ms;
		long writer_max_pause_ms;
		int reader_threads;
		long reader_min_pause_ms;
		long reader_max_pause_ms;

		// Default settings
		if (args.length == 1) {
			if (!args[0].equalsIgnoreCase("default")) {
				printUsage();
				return;
			}

			writer_threads = 2;
			writer_min_pause_ms = 0;
			writer_max_pause_ms = 10;

			reader_threads = 20;
			reader_min_pause_ms = 0;
			reader_max_pause_ms = 0;
		} else {
			writer_threads = Integer.parseInt(args[0]);
			writer_min_pause_ms = Long.parseLong(args[1]);
			writer_max_pause_ms = Long.parseLong(args[2]);
			reader_threads = Integer.parseInt(args[3]);
			reader_min_pause_ms = Long.parseLong(args[4]);
			reader_max_pause_ms = Long.parseLong(args[5]);
		}

		// Setup
		EventList list = new BasicEventList();
		list.add("one");
		list.add("two");
		list.add("three");

		System.out.println("ReadWriteLock class: " +
			LockFactory.DEFAULT.createReadWriteLock().getClass().getName());

		System.out.print("Starting threads...");

		final WriterThread[] writers = new WriterThread[ writer_threads ];
		for (int i = 0; i < writers.length; i++) {
			writers[i] = new WriterThread(list, writer_min_pause_ms, writer_max_pause_ms);
			writers[i].start();
		}

		final ReaderThread[] readers = new ReaderThread[ reader_threads ];
		for (int i = 0; i < readers.length; i++) {
			readers[i] = new ReaderThread(list, reader_min_pause_ms, reader_max_pause_ms);
			readers[i].start();
		}
		System.out.println("done.");

		System.out.print("Waiting one minute...");
		try {
			Thread.sleep(60000);
		}
		catch (InterruptedException ex) {
		}
		System.out.println("done.");

		System.out.print("Stopping threads...");
		// Stop everything
		for (int i = 0; i < writers.length; i++)
			writers[i].interrupt();
		for (int i = 0; i < readers.length; i++)
			readers[i].interrupt();

		// Wait for them to stop
		try {
			for (int i = 0; i < writers.length; i++)
				writers[i].join();
			for (int i = 0; i < readers.length; i++)
				readers[i].join();
		}
		catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		System.out.println("done.");
		System.out.println("");

		long total_num_ops = 0;
		long total_time = 0;
		for (int i = 0; i < writers.length; i++) {
			total_num_ops += writers[i].getNumOps();
			total_time += writers[i].getTotalTime();
		}
		System.out.println("Total writes: " + total_num_ops);
		System.out.println("Total write time: " + total_time);


		total_num_ops = 0;
		total_time = 0;
		for (int i = 0; i < readers.length; i++) {
			total_num_ops += readers[i].getNumOps();
			total_time += readers[i].getTotalTime();
		}
		System.out.println("Total reads: " + total_num_ops);
		System.out.println("Total read time: " + total_time);

	}


	private static void printUsage() {
		System.out.println("Usage: ThreadContentionPerformance <num_writer_threads> " +
			"<writer_min_wait_ms> <writer_max_wait_ms> <num_reader_threads> " +
			"<reader_min_wait_ms> <reader_max_wait_ms>");
		System.out.println("   or  ThreadContentionPerformance default");
	}


	private static class ReaderThread extends Thread {
		private final EventList list;
		private final Random random;

		private final int wait_diff;
		private final long min_wait;

		private long total_time = 0;
		private long num_ops = 0;

		ReaderThread(EventList list, long min_wait, long max_wait) {
			super("ReaderThread");

			this.list = list;

			this.wait_diff = (int) (max_wait - min_wait);
			this.min_wait = min_wait;

			this.random = new Random();
		}


		synchronized long getTotalTime() {
			return total_time;
		}

		synchronized long getNumOps() {
			return num_ops;
		}

		public void run() {
			while (!isInterrupted()) {
				// Wait
				long sleep = min_wait + wait_diff == 0 ? 0 : random.nextInt(wait_diff);
				try {
					Thread.sleep(sleep);
				}
				catch (InterruptedException ex) {
					return;
				}

				// Read something
				long time = System.currentTimeMillis();
//				long time = System.nanoTime();
				list.getReadWriteLock().readLock().lock();
				try {
					int size = list.size();

					// Get a random object
					Object obj = list.get(random.nextInt(size));
				}
				finally {
					list.getReadWriteLock().readLock().unlock();

					total_time += System.currentTimeMillis() - time;
//					total_time += ( System.nanoTime() - time );
					num_ops++;
				}
			}
		}
	}


	private static class WriterThread extends Thread {
		private final EventList list;
		private final Random random;

		private final int wait_diff;
		private final long min_wait;

		private long total_time = 0;
		private long num_ops = 0;

		WriterThread(EventList list, long min_wait, long max_wait) {
			super("WriterThread");

			setPriority(Thread.NORM_PRIORITY + 2);

			this.list = list;

			this.wait_diff = (int) (max_wait - min_wait);
			this.min_wait = min_wait;

			this.random = new Random();
		}


		synchronized long getTotalTime() {
			return total_time;
		}

		synchronized long getNumOps() {
			return num_ops;
		}


		public void run() {
			while (!isInterrupted()) {
				// Wait
				long sleep = min_wait + wait_diff == 0 ? 0 : random.nextInt(wait_diff);

				try {
					Thread.sleep(sleep);
				}
				catch (InterruptedException ex) {
					return;
				}

				// Write something
				long time = System.currentTimeMillis();
//				long time = System.nanoTime();
				list.getReadWriteLock().writeLock().lock();
				try {
					list.add(new Long(System.currentTimeMillis()));
				}
				finally {
					list.getReadWriteLock().writeLock().unlock();

					total_time += System.currentTimeMillis() - time;
//					total_time += ( System.nanoTime() - time );
					num_ops++;
				}
			}
		}
	}
}
