diff --git a/stormpot-benchmark/pom.xml b/stormpot-benchmark/pom.xml
index d095d687..1602b1a5 100644
--- a/stormpot-benchmark/pom.xml
+++ b/stormpot-benchmark/pom.xml
@@ -14,5 +14,13 @@
stormpot-parent
2.1-SNAPSHOT
+
+
+
+ ${groupId}
+ stormpot
+ ${version}
+
+
diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/Bench.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/Bench.java
new file mode 100644
index 00000000..9d3f021f
--- /dev/null
+++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/Bench.java
@@ -0,0 +1,51 @@
+package stormpot.benchmark;
+
+public abstract class Bench {
+ public abstract void primeWithSize(int size) throws Exception;
+ public abstract Object claim() throws Exception;
+ public abstract void release(Object object) throws Exception;
+ public abstract void claimAndRelease() throws Exception;
+
+ private int trials;
+ private long timeSum;
+ private long timeMin = Long.MAX_VALUE;
+ private long timeMax = Long.MIN_VALUE;
+ private long period;
+
+ public final void recordTime(long time) {
+ trials++;
+ timeSum += time;
+ timeMin = Math.min(timeMin, time);
+ timeMax = Math.max(timeMax, time);
+ }
+
+ public final void recordPeriod(long period) {
+ this.period = period;
+ }
+
+ public final void reset() {
+ trials = 0;
+ timeSum = 0;
+ timeMin = Long.MAX_VALUE;
+ timeMax = Long.MIN_VALUE;
+ period = 0;
+ }
+
+ public final void report() {
+ String name = getName();
+ double cyclesPerSec = (1000.0 / period) * trials;
+ double timeMean = ((double) timeSum) / trials;
+
+ String str = "Benchmark: %s\t" +
+ "%s trials\t" +
+ "%.1f claim+release/sec\t" +
+ "latency(max, mean, min) = " +
+ "(% 3d, %.6f, %s) in millis.\n";
+ System.out.printf(
+ str, name, trials, cyclesPerSec, timeMax, timeMean, timeMin);
+ }
+
+ public final String getName() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/Clock.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/Clock.java
new file mode 100644
index 00000000..14a51180
--- /dev/null
+++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/Clock.java
@@ -0,0 +1,95 @@
+package stormpot.benchmark;
+
+import java.util.concurrent.locks.LockSupport;
+
+/**
+ * The Clock improves upon {@link System#currentTimeMillis()} in that it can be
+ * stopped and manually ticked forward. This is useful for testability.
+ * @author cvh
+ *
+ */
+public final class Clock {
+
+ private static volatile long now;
+
+ private static final Ticker ticker = new Ticker();
+
+ private Clock() { /* static utility class */ }
+
+ /**
+ * Advance the clock to the current time.
+ * @return The new current time, in milliseconds since the epoch.
+ */
+ public static long tick() {
+ return now = System.currentTimeMillis();
+ }
+
+ /**
+ * Advance the clock by exactly one millisecond, regardless of what the
+ * actual time may be.
+ * Note that this method is racy, and is technically only safe to call by
+ * a single thread at a time, and when the automatic ticking of the clock
+ * has been turned off. This method is only really useful for unit testing of
+ * time dependent code.
+ */
+ public static void inc() {
+ now++;
+ }
+
+ /**
+ * Get the value of the last tick.
+ * @return The "current" time recorded by the last tick.
+ */
+ public static long currentTimeMillis() {
+ return now;
+ }
+
+ /**
+ * Start automatic ticking. This will tick the clock, and update its current
+ * time value, about every 10 milliseconds. Note that this does not guarantee
+ * that the clock will get a distinctively new time value every 10
+ * milliseconds. The clock can be stopped again at any time with the
+ * {@link #stop()} method. Starting an already started clock has no effect.
+ */
+ public static void start() {
+ tick();
+ ticker.stopFlag = false;
+ if (ticker.getState() == Thread.State.NEW) {
+ ticker.start();
+ } else {
+ LockSupport.unpark(ticker);
+ }
+ }
+
+ /**
+ * Stop automatic ticking. It can be resumed again with the {@link #start()}
+ * method. Stopping an already stopped clock has no effect.
+ */
+ public static void stop() {
+ ticker.stopFlag = true;
+ }
+
+ private static final class Ticker extends Thread {
+ public volatile boolean stopFlag;
+
+ public Ticker() {
+ super("Clock-Thread");
+ setDaemon(true);
+ }
+
+ @Override
+ public void run() {
+ for (;;) {
+ if (stopFlag) {
+ LockSupport.park();
+ }
+ tick();
+// LockSupport.parkNanos(1000); // 1 millis
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+}
diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/QueuePoolBench.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/QueuePoolBench.java
new file mode 100644
index 00000000..e76ef946
--- /dev/null
+++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/QueuePoolBench.java
@@ -0,0 +1,37 @@
+package stormpot.benchmark;
+
+import java.util.concurrent.TimeUnit;
+
+import stormpot.Config;
+import stormpot.Timeout;
+import stormpot.qpool.QueuePool;
+
+public class QueuePoolBench extends Bench {
+ private static final Timeout timeout =
+ new Timeout(1000, TimeUnit.MILLISECONDS);
+
+ private QueuePool pool;
+
+ @Override
+ public void primeWithSize(int size) {
+ Config config = new Config();
+ config.setAllocator(new StormpotPoolableAllocator());
+ config.setSize(size);
+ pool = new QueuePool(config);
+ }
+
+ @Override
+ public Object claim() throws Exception {
+ return pool.claim(timeout);
+ }
+
+ @Override
+ public void release(Object object) throws Exception {
+ ((StormpotPoolable) object).release();
+ }
+
+ @Override
+ public void claimAndRelease() throws Exception {
+ release(claim());
+ }
+}
diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolable.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolable.java
new file mode 100644
index 00000000..64c15040
--- /dev/null
+++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolable.java
@@ -0,0 +1,17 @@
+package stormpot.benchmark;
+
+import stormpot.Poolable;
+import stormpot.Slot;
+
+public class StormpotPoolable implements Poolable {
+ private Slot slot;
+
+ public StormpotPoolable(Slot slot) {
+ this.slot = slot;
+ }
+
+ @Override
+ public void release() {
+ slot.release(this);
+ }
+}
diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolableAllocator.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolableAllocator.java
new file mode 100644
index 00000000..dc78cad1
--- /dev/null
+++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolableAllocator.java
@@ -0,0 +1,16 @@
+package stormpot.benchmark;
+
+import stormpot.Allocator;
+import stormpot.Poolable;
+import stormpot.Slot;
+
+public class StormpotPoolableAllocator implements Allocator {
+ @Override
+ public Poolable allocate(Slot slot) throws Exception {
+ return new StormpotPoolable(slot);
+ }
+
+ @Override
+ public void deallocate(Poolable poolable) throws Exception {
+ }
+}
diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/Throughput.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/Throughput.java
new file mode 100644
index 00000000..aa9c691b
--- /dev/null
+++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/Throughput.java
@@ -0,0 +1,80 @@
+package stormpot.benchmark;
+
+/**
+ * After warm-up, how many times can we claim and release non-expiring objects
+ * in a given timeframe?
+ * @author cvh
+ */
+public class Throughput {
+ private static final int SIZE = 10;
+ private static final long TRIAL_TIME_MILLIS = 500L;
+
+ public static void main(String[] args) throws Exception {
+ Clock.start();
+ System.out.println("Stormpot Single-Threaded Throughput Benchmark");
+ try {
+ runBenchmark();
+ } finally {
+ System.exit(0);
+ }
+ }
+
+ private static void runBenchmark() throws Exception {
+ Bench queuePool = new QueuePoolBench();
+ queuePool.primeWithSize(SIZE);
+
+ warmup(queuePool);
+ trial(queuePool);
+ trial(queuePool);
+ trial(queuePool);
+ trial(queuePool);
+ trial(queuePool);
+ trial(queuePool);
+ trial(queuePool);
+ trial(queuePool);
+ trial(queuePool);
+ trial(queuePool);
+ }
+
+ private static void warmup(Bench bench) throws Exception {
+ System.out.println("Warming up " + bench.getName());
+ int steps = 20;
+ for (int i = 0; i < steps; i++) {
+ for (int j = 0; j < 1000; j++) {
+ benchmark(bench, 1);
+ }
+ System.out.printf("%02d/%s.", i, steps);
+ }
+ System.out.println("\nWarmup done.");
+ }
+
+ private static void trial(Bench bench) throws Exception {
+ Thread.sleep(10);
+ benchmark(bench, TRIAL_TIME_MILLIS);
+ bench.report();
+ }
+
+ private static void benchmark(Bench bench, long trialTimeMillis) throws Exception {
+ bench.reset();
+
+ long start = Clock.currentTimeMillis();
+ long deadline = start + trialTimeMillis;
+ long end = 0L;
+ do {
+ end = runCycles(bench, 100);
+ } while (end < deadline);
+ bench.recordPeriod(end - start);
+ }
+
+ private static long runCycles(Bench bench, int cycles) throws Exception {
+ long start;
+ long end = 0;
+ for (int i = 0; i < cycles; i++) {
+ start = Clock.currentTimeMillis();
+ bench.claimAndRelease();
+ end = Clock.currentTimeMillis();
+ bench.recordTime(end - start);
+ }
+ return end;
+ }
+}