StopwatchImpl -- improved -- by Erik van Oosten

This code accompanies the article Evaluating Simon.

Quick download link:
StopwatchImpl -- improved --.java

package org.javasimon;

import org.javasimon.utils.SimonUtils;

import java.util.*;

/**
 * Class implements {@link org.javasimon.Stopwatch} interface - see there for how to use Stopwatch.
 *
 * @see org.javasimon.Stopwatch
 *
 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a>
 * @created Aug 4, 2008
 */
final class StopwatchImpl extends AbstractSimon implements Stopwatch {
	private long total;

	private long counter;

	private long active;

	private long max;

	private long maxTimestamp;

	private long maxActive;

	private long maxActiveTimestamp;

	private long min = Long.MAX_VALUE;

	private long minTimestamp;

	private long last;

	private ThreadLocal<Long> threadBasedSplitMap = new ThreadLocal<Long>();

	private Map<Object, Long> splitMap = new HashMap<Object, Long>();

	private long firstUsageNanos;

	private long currentNanos;

	StopwatchImpl(String name) {
		super(name);
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized Stopwatch addTime(long ns) {
		if (enabled) {
			updateUsages();
			addSplit(ns);
		}
		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	public Stopwatch start() {
		if (enabled) {
//			if (threadBasedSplitMap.get() != null) {
//				throw new SimonException("Illegal start - there is another split running in the current thread: " + Thread.currentThread().getId());
//			}
//			updateUsages();
//			activeStart();
//			threadBasedSplitMap.set(currentNanos);
            return new StopwatchMeasurement(System.nanoTime());
        }
		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized long stop() {
		if (enabled) {
            throw new UnsupportedOperationException();
//			Long start = threadBasedSplitMap.get();
//			if (start == null) {
//				return 0;
//			}
//			active--;
//			threadBasedSplitMap.remove();
//			updateUsages();
//			return addSplit(currentNanos - start);
		}
		return 0;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized Stopwatch start(Object key) {
		if (enabled) {
			if (splitMap.containsKey(key)) {
				throw new SimonException("Illegal start - there is another split running for the specified key: " + key);
			}
			updateUsages();
			activeStart();
			splitMap.put(key, currentNanos);
		}
		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized long stop(Object key) {
		if (enabled) {
			Long start = splitMap.remove(key);
			if (start == null) {
				return 0;
			}
			active--;
			updateUsages();
			return addSplit(currentNanos - start);
		}
		return 0;
	}

	// Uses last usage, hence it must be placed after usages update
	private void activeStart() {
		active++;
		if (active >= maxActive) {
			maxActive = active;
			maxActiveTimestamp = getLastUsage();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized Stopwatch reset() {
		total = 0;
		counter = 0;
		max = 0;
		min = Long.MAX_VALUE;
		maxTimestamp = 0;
		minTimestamp = 0;
		// active is not reset, because
		maxActive = 0;
		maxActiveTimestamp = 0;
		getStatProcessor().reset();
		return this;
	}

	private long addSplit(long split) {
		last = split;
		total += split;
		counter++;
		if (split > max) {
			max = split;
			maxTimestamp = getLastUsage();
		}
		if (split < min) {
			min = split;
			minTimestamp = getLastUsage();
		}
		if (getStatProcessor() != null) {
			getStatProcessor().process(split);
		}
		return split;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized long getTotal() {
		return total;
	}

	/**
	 * {@inheritDoc}
	 */
	public long getLast() {
		return last;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized long getCounter() {
		return counter;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized long getMax() {
		return max;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized long getMin() {
		return min;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized long getMaxTimestamp() {
		return maxTimestamp;
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized long getMinTimestamp() {
		return minTimestamp;
	}

	/**
	 * {@inheritDoc}
	 */
	public long getActive() {
		return active;
	}

	/**
	 * {@inheritDoc}
	 */
	public long getMaxActive() {
		return maxActive;
	}

	/**
	 * {@inheritDoc}
	 */
	public long getMaxActiveTimestamp() {
		return maxActiveTimestamp;
	}

	@Override
	protected void disabledObserver() {
		active = 0;
		threadBasedSplitMap = new ThreadLocal<Long>();
		splitMap.clear();
	}

	@Override
	public void setStatProcessor(StatProcessor statProcessor) {
		super.setStatProcessor(statProcessor);
		statProcessor.setInterpreter(StatProcessor.NanoInterpreter.INSTANCE);
	}

	/**
	 * {@inheritDoc}
	 */
	public synchronized Map<String, String> sample(boolean reset) {
		Map<String, String> map = new LinkedHashMap<String, String>();
		map.put("total", String.valueOf(total));
		map.put("counter", String.valueOf(counter));
		map.put("min", String.valueOf(min));
		map.put("max", String.valueOf(max));
		map.put("minTimestamp", String.valueOf(minTimestamp));
		map.put("maxTimestamp", String.valueOf(maxTimestamp));
		map.putAll(getStatProcessor().sample(false)); // reset is done via Simon's reset method
		if (reset) {
			reset();
		}
		return map;
	}

	/**
	 * Updates usage statistics.
	 */
	protected void updateUsages() {
		currentNanos = System.nanoTime();
		if (firstUsage == 0) {
			firstUsage = System.currentTimeMillis();
			firstUsageNanos = currentNanos;
		}
		lastUsage = firstUsage + (currentNanos - firstUsageNanos) / SimonUtils.NANOS_IN_MILLIS;
	}

	@Override
	public String toString() {
		return "Simon Stopwatch: " + super.toString() +
			" total " + SimonUtils.presentNanoTime(total) +
			", counter " + counter +
			", max " + SimonUtils.presentNanoTime(max) +
			", min " + SimonUtils.presentNanoTime(min) +
			(getNote() != null && getNote().length() != 0 ? ", note '" + getNote() + "'" : "");
	}

    private class StopwatchMeasurement implements Stopwatch {
        private long startNanos;
        public StopwatchMeasurement(long startNanos) {
            this.startNanos = startNanos;
        }

        public long stop() {
            long split = System.nanoTime() - startNanos;
            synchronized (StopwatchImpl.this) {
                return addSplit(split);
            }
        }

        public Stopwatch addTime(long ns) {
            throw new UnsupportedOperationException();
        }

        public Stopwatch start() {
            throw new UnsupportedOperationException();
        }

        public Stopwatch start(Object key) {
            throw new UnsupportedOperationException();
        }

        public long stop(Object key) {
            throw new UnsupportedOperationException();
        }

        public long getTotal() {
            throw new UnsupportedOperationException();
        }

        public long getLast() {
            throw new UnsupportedOperationException();
        }

        public long getCounter() {
            throw new UnsupportedOperationException();
        }

        public long getMax() {
            throw new UnsupportedOperationException();
        }

        public long getMin() {
            throw new UnsupportedOperationException();
        }

        public long getMaxTimestamp() {
            throw new UnsupportedOperationException();
        }

        public long getMinTimestamp() {
            throw new UnsupportedOperationException();
        }

        public Simon getParent() {
            throw new UnsupportedOperationException();
        }

        public Collection<Simon> getChildren() {
            throw new UnsupportedOperationException();
        }

        public String getName() {
            throw new UnsupportedOperationException();
        }

        public SimonState getState() {
            throw new UnsupportedOperationException();
        }

        public void setState(SimonState state, boolean overrule) {
            throw new UnsupportedOperationException();
        }

        public boolean isEnabled() {
            throw new UnsupportedOperationException();
        }

        public Stopwatch reset() {
            throw new UnsupportedOperationException();
        }

        public StatProcessor getStatProcessor() {
            throw new UnsupportedOperationException();
        }

        public void setStatProcessor(StatProcessor statProcessor) {
            throw new UnsupportedOperationException();
        }

        public String getNote() {
            throw new UnsupportedOperationException();
        }

        public void setNote(String note) {
            throw new UnsupportedOperationException();
        }

        public long getFirstUsage() {
            throw new UnsupportedOperationException();
        }

        public long getLastUsage() {
            throw new UnsupportedOperationException();
        }

        public Map<String, String> sample(boolean reset) {
            throw new UnsupportedOperationException();
        }

        public long getActive() {
            throw new UnsupportedOperationException();
        }

        public long getMaxActive() {
            throw new UnsupportedOperationException();
        }

        public long getMaxActiveTimestamp() {
            throw new UnsupportedOperationException();
        }
    }
}