diff --git a/aws/s3/core/src/main/java/org/jclouds/aws/s3/util/DateService.java b/aws/s3/core/src/main/java/org/jclouds/aws/s3/util/DateService.java
index d9ddcac0c8..7ab059b0bb 100644
--- a/aws/s3/core/src/main/java/org/jclouds/aws/s3/util/DateService.java
+++ b/aws/s3/core/src/main/java/org/jclouds/aws/s3/util/DateService.java
@@ -37,6 +37,8 @@ import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
+import com.google.common.annotations.VisibleForTesting;
+
/**
* Parses and formats the ISO8601 and RFC822 date formats found in
* XML responses and HTTP response headers.
@@ -112,7 +114,7 @@ public class DateService {
return iso8601DateFormat(new DateTime());
}
- public final DateTime iso8601DateParse(String toParse) {
+ public final DateTime iso8601DateParse(String toParse) {
synchronized (iso8601SimpleDateFormat) {
try {
return new DateTime(iso8601SimpleDateFormat.parse(toParse));
@@ -122,4 +124,22 @@ public class DateService {
}
}
+ /*
+ * Alternative implementations of Format and Parse -- used to
+ * test relative speeds.
+ * TODO: Remove methods below once sufficient performance testing is complete.
+ */
+
+ @VisibleForTesting
+ public final DateTime jodaIso8601DateParse(String toParse) {
+ return new DateTime(toParse);
+ }
+
+ @VisibleForTesting
+ public final String sdfIso8601DateFormat(DateTime dateTime) {
+ synchronized (iso8601SimpleDateFormat) {
+ return iso8601SimpleDateFormat.format(dateTime.toDate());
+ }
+ }
+
}
diff --git a/aws/s3/core/src/test/java/org/jclouds/aws/PerformanceTest.java b/aws/s3/core/src/test/java/org/jclouds/aws/PerformanceTest.java
index 6cad97f4b2..17a09f6fa3 100644
--- a/aws/s3/core/src/test/java/org/jclouds/aws/PerformanceTest.java
+++ b/aws/s3/core/src/test/java/org/jclouds/aws/PerformanceTest.java
@@ -23,6 +23,12 @@
*/
package org.jclouds.aws;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -38,6 +44,7 @@ import org.testng.annotations.Test;
@Test(groups = "performance")
public class PerformanceTest {
protected static int LOOP_COUNT = 1000;
+ protected static int THREAD_COUNT = 1000;
protected ExecutorService exec;
@BeforeTest
@@ -50,4 +57,78 @@ public class PerformanceTest {
exec.shutdownNow();
exec = null;
}
+
+ /**
+ * Executes a list of Runnable tasks in {@link #THREAD_COUNT}
+ * simultaneous threads, and outputs the timing results.
+ *
+ * This method is careful to time only the actual task execution
+ * time, not the overhead of creating and queuing the tasks.
+ * We also use CountDownLatches to ensure that all tasks start
+ * at the same time, so concurrency is fully tested without
+ * ramp-up or ramp-down times.
+ *
+ * This code is heavily based on Listing 5.11 in
+ * "Java Concurrency in Practice" by Brian Goetz et al,
+ * Addison-Wesley Professional.
+ *
+ * @see {@link DateServiceTest} for example usage.
+ *
+ * @param performanceTestName
+ * @param tasks
+ * @throws InterruptedException
+ * @throws ExecutionException
+ * @throws Throwable
+ */
+ protected void executeMultiThreadedPerformanceTest(String performanceTestName, List tasks)
+ throws InterruptedException, ExecutionException, Throwable
+ {
+ CompletionService completer = new ExecutorCompletionService(exec);
+ final CountDownLatch startGate = new CountDownLatch(1);
+ final CountDownLatch endGate = new CountDownLatch(THREAD_COUNT);
+
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ final Runnable task = tasks.get(i % tasks.size());
+ // Wrap task so we can count down endGate.
+ completer.submit(new Callable() {
+ public Throwable call() {
+ try {
+ startGate.await(); // Wait to start simultaneously
+ task.run();
+ return null;
+ } catch (Throwable t) {
+ return t;
+ } finally {
+ endGate.countDown(); // Notify that I've finished
+ }
+ }
+ });
+ }
+
+ // Only time the execution time for all tasks, not start/stop times.
+ long startTime = System.nanoTime();
+ startGate.countDown(); // Trigger start of all tasks
+ endGate.await();
+ long endTime = System.nanoTime() - startTime;
+
+ // Check for assertion failures
+ Throwable t;
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ t = completer.take().get();
+ if (t != null) {
+ throw t;
+ }
+ }
+ if (performanceTestName != null) {
+ System.out.printf("TIMING: Multi-threaded %s took %.3fms for %d threads\n",
+ performanceTestName, ((double)endTime / 1000000), THREAD_COUNT);
+ }
+ }
+
+ protected void executeMultiThreadedCorrectnessTest(List tasks)
+ throws InterruptedException, ExecutionException, Throwable
+ {
+ executeMultiThreadedPerformanceTest(null, tasks);
+ }
+
}
diff --git a/aws/s3/perftest/src/test/java/com/amazon/s3/DateServiceTest.java b/aws/s3/perftest/src/test/java/com/amazon/s3/DateServiceTest.java
index 83f703b3ba..96ba637dc0 100644
--- a/aws/s3/perftest/src/test/java/com/amazon/s3/DateServiceTest.java
+++ b/aws/s3/perftest/src/test/java/com/amazon/s3/DateServiceTest.java
@@ -24,21 +24,14 @@
package com.amazon.s3;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-import java.util.Date;
-import java.util.Locale;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CompletionService;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorCompletionService;
import org.jclouds.aws.PerformanceTest;
import org.jclouds.aws.s3.util.DateService;
import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.DateTimeFormatter;
import org.testng.annotations.Test;
import com.google.inject.Guice;
@@ -66,71 +59,40 @@ public class DateServiceTest extends PerformanceTest {
class TestData {
public final String iso8601DateString;
public final String rfc822DateString;
- public final Date date;
+ public final DateTime date;
- TestData(String iso8601, String rfc822, Date date) {
+ TestData(String iso8601, String rfc822, DateTime dateTime) {
this.iso8601DateString = iso8601;
this.rfc822DateString = rfc822;
- this.date = date;
+ this.date = dateTime;
}
}
- private long startTime;
-
public DateServiceTest() {
// Constant time test values, each TestData item must contain matching times!
testData = new TestData[] {
- new TestData("2009-03-12T02:00:07.000Z", "Thu, 12 Mar 2009 02:00:07 GMT", new Date(
- 1236823207000l)),
- new TestData("2009-03-14T04:00:07.000Z", "Sat, 14 Mar 2009 04:00:07 GMT", new Date(
- 1237003207000l)),
- new TestData("2009-03-16T06:00:07.000Z", "Mon, 16 Mar 2009 06:00:07 GMT", new Date(
- 1237183207000l)),
- new TestData("2009-03-18T08:00:07.000Z", "Wed, 18 Mar 2009 08:00:07 GMT", new Date(
- 1237363207000l)),
- new TestData("2009-03-20T10:00:07.000Z", "Fri, 20 Mar 2009 10:00:07 GMT", new Date(
- 1237543207000l)) };
- }
-
- // Joda items for performance comparisons
- private DateTimeFormatter headerDateFormat = DateTimeFormat.forPattern(
- "EEE, dd MMM yyyy HH:mm:ss 'GMT'").withLocale(Locale.US);
-
- private DateTime jodaParseIso8601(String toParse) {
- return new DateTime(toParse);
- }
-
- private DateTime jodaParseRfc822(String toParse) {
- return headerDateFormat.parseDateTime(toParse);
- }
-
- private String timestampAsHeaderString() {
- return toHeaderString(new DateTime());
- }
-
- private String toHeaderString(DateTime date) {
- return headerDateFormat.print(date.withZone(DateTimeZone.UTC));
- }
-
- private void startClock() {
- startTime = System.currentTimeMillis();
- }
-
- private void printElapsedClockTime(String testName) {
- System.out.println(testName + " took " + (System.currentTimeMillis() - startTime) + "ms for "
- + LOOP_COUNT + " loops");
+ new TestData("2009-03-12T02:00:07.000Z", "Thu, 12 Mar 2009 02:00:07 GMT",
+ new DateTime(1236823207000l)),
+ new TestData("2009-03-14T04:00:07.000Z", "Sat, 14 Mar 2009 04:00:07 GMT",
+ new DateTime(1237003207000l)),
+ new TestData("2009-03-16T06:00:07.000Z", "Mon, 16 Mar 2009 06:00:07 GMT",
+ new DateTime(1237183207000l)),
+ new TestData("2009-03-18T08:00:07.000Z", "Wed, 18 Mar 2009 08:00:07 GMT",
+ new DateTime(1237363207000l)),
+ new TestData("2009-03-20T10:00:07.000Z", "Fri, 20 Mar 2009 10:00:07 GMT",
+ new DateTime(1237543207000l)) };
}
@Test
public void testIso8601DateParse() throws ExecutionException, InterruptedException {
DateTime dsDate = dateService.iso8601DateParse(testData[0].iso8601DateString);
- assertEquals(dsDate.toDate(), testData[0].date);
+ assertEquals(dsDate, testData[0].date);
}
@Test
public void testRfc822DateParse() throws ExecutionException, InterruptedException {
DateTime dsDate = dateService.rfc822DateParse(testData[0].rfc822DateString);
- assertEquals(dsDate.toDate(), testData[0].date);
+ assertEquals(dsDate, testData[0].date);
}
@Test
@@ -158,69 +120,72 @@ public class DateServiceTest extends PerformanceTest {
}
@Test
- void testFormatIso8601DateInParallel() throws InterruptedException, ExecutionException {
- CompletionService completer = new ExecutorCompletionService(exec);
- startClock();
- for (int i = 0; i < LOOP_COUNT; i++) {
- final TestData myData = testData[i % testData.length];
- completer.submit(new Callable() {
- public Boolean call() throws ExecutionException, InterruptedException {
- String dsString = dateService.iso8601DateFormat(myData.date);
- /*
- * Comment-in the assert below to test thread safety. Comment it out to test
- * performance
- */
- assertEquals(dsString, myData.iso8601DateString);
- return true;
- }
- });
+ void testFormatIso8601DateCorrectnessInParallel() throws Throwable {
+ List tasks = new ArrayList(testData.length);
+ for (final TestData myData: testData) {
+ tasks.add(new Runnable() {
+ public void run() {
+ String dsString = dateService.iso8601DateFormat(myData.date);
+ assertEquals(dsString, myData.iso8601DateString);
+ }
+ });
}
- for (int i = 0; i < LOOP_COUNT; i++)
- assertTrue(completer.take().get());
- printElapsedClockTime("testFormatIso8601DateInParallel");
+ executeMultiThreadedCorrectnessTest(tasks);
}
@Test
- void testFormatAmazonDateInParallel() throws InterruptedException, ExecutionException {
- CompletionService completer = new ExecutorCompletionService(exec);
- startClock();
- for (int i = 0; i < LOOP_COUNT; i++)
- completer.submit(new Callable() {
- public Boolean call() {
- AWSAuthConnection.httpDate();
- return true;
- }
- });
- for (int i = 0; i < LOOP_COUNT; i++)
- assertTrue(completer.take().get());
- printElapsedClockTime("testFormatAmazonDateInParallel");
- }
-
- @Test
- void testFormatJodaDateInParallel() throws InterruptedException, ExecutionException {
- CompletionService completer = new ExecutorCompletionService(exec);
- startClock();
- for (int i = 0; i < LOOP_COUNT; i++) {
- final TestData myData = testData[i % testData.length];
- completer.submit(new Callable() {
- public Boolean call() {
- String jodaString = toHeaderString(new DateTime(myData.date));
- assertEquals(jodaString, myData.rfc822DateString);
- return true;
- }
- });
+ void testFormatIso8601DatePerformanceInParallel() throws Throwable {
+ List tasks = new ArrayList(testData.length);
+ for (final TestData myData: testData) {
+ tasks.add(new Runnable() {
+ public void run() {
+ dateService.iso8601DateFormat(myData.date);
+ }
+ });
}
- for (int i = 0; i < LOOP_COUNT; i++)
- assertTrue(completer.take().get());
- printElapsedClockTime("testFormatJodaDateInParallel");
+ executeMultiThreadedPerformanceTest("testFormatIso8601DatePerformanceInParallel", tasks);
}
-
+
@Test
- void testIso8601ParseDateSerialResponseTime() throws ExecutionException, InterruptedException {
+ void testFormatIso8601DatePerformanceInParallel_SdfAlternative() throws Throwable {
+ List tasks = new ArrayList(testData.length);
+ for (final TestData myData: testData) {
+ tasks.add(new Runnable() {
+ public void run() {
+ dateService.sdfIso8601DateFormat(myData.date);
+ }
+ });
+ }
+ executeMultiThreadedPerformanceTest(
+ "testFormatIso8601DatePerformanceInParallel_SdfAlternative", tasks);
+ }
+
+ @Test
+ void testFormatAmazonDatePerformanceInParallel() throws Throwable {
+ List tasks = new ArrayList(testData.length);
+ tasks.add(
+ new Runnable() {
+ public void run() {
+ AWSAuthConnection.httpDate();
+ }}
+ );
+ executeMultiThreadedPerformanceTest("testFormatAmazonDatePerformanceInParallel", tasks);
+ }
+
+ @Test
+ void testParseIso8601DateSerialResponseTime() throws ExecutionException, InterruptedException {
for (int i = 0; i < LOOP_COUNT; i++)
dateService.iso8601DateParse(testData[0].iso8601DateString);
}
+ @Test
+ void testParseIso8601DateSerialResponseTime_JodaAlternative()
+ throws ExecutionException, InterruptedException
+ {
+ for (int i = 0; i < LOOP_COUNT; i++)
+ dateService.jodaIso8601DateParse(testData[0].iso8601DateString);
+ }
+
@Test
void testAmazonParseDateSerialResponseTime() {
for (int i = 0; i < LOOP_COUNT; i++)
@@ -228,57 +193,44 @@ public class DateServiceTest extends PerformanceTest {
}
@Test
- void testParseIso8601DateInParallel() throws InterruptedException, ExecutionException {
- CompletionService completer = new ExecutorCompletionService(exec);
- startClock();
- for (int i = 0; i < LOOP_COUNT; i++) {
- final TestData myData = testData[i % testData.length];
- completer.submit(new Callable() {
- public Boolean call() throws ExecutionException, InterruptedException {
- DateTime dsDate = dateService.iso8601DateParse(myData.iso8601DateString);
- assertEquals(dsDate.toDate(), myData.date);
- return true;
- }
- });
+ void testParseIso8601DateCorrectnessInParallel() throws Throwable {
+ List tasks = new ArrayList(testData.length);
+ for (final TestData myData: testData) {
+ tasks.add(new Runnable() {
+ public void run() {
+ DateTime dsDate = dateService.iso8601DateParse(myData.iso8601DateString);
+ assertEquals(dsDate, myData.date);
+ }
+ });
}
- for (int i = 0; i < LOOP_COUNT; i++)
- assertTrue(completer.take().get());
- printElapsedClockTime("testParseIso8601DateInParallel");
+ executeMultiThreadedCorrectnessTest(tasks);
}
@Test
- void testParseAmazonDateInParallel() throws InterruptedException, ExecutionException {
- CompletionService completer = new ExecutorCompletionService(exec);
- startClock();
- for (int i = 0; i < LOOP_COUNT; i++)
- completer.submit(new Callable() {
- public Boolean call() {
- AWSAuthConnection.httpDate();
- return true;
- }
- });
- for (int i = 0; i < LOOP_COUNT; i++)
- assertTrue(completer.take().get());
- printElapsedClockTime("testParseAmazonDateInParallel");
+ void testParseIso8601DatePerformanceInParallel() throws Throwable {
+ List tasks = new ArrayList(testData.length);
+ for (final TestData myData: testData) {
+ tasks.add(new Runnable() {
+ public void run() {
+ dateService.iso8601DateParse(myData.iso8601DateString);
+ }
+ });
+ }
+ executeMultiThreadedPerformanceTest("testParseIso8601DatePerformanceInParallel", tasks);
}
@Test
- void testParseJodaDateInParallel() throws InterruptedException, ExecutionException {
- CompletionService completer = new ExecutorCompletionService(exec);
- startClock();
- for (int i = 0; i < LOOP_COUNT; i++) {
- final TestData myData = testData[i % testData.length];
- completer.submit(new Callable() {
- public Boolean call() {
- Date jodaDate = jodaParseIso8601(myData.iso8601DateString).toDate();
- assertEquals(jodaDate, myData.date);
- return true;
- }
- });
+ void testParseIso8601DatePerformanceInParallel_JodaAlternative() throws Throwable {
+ List tasks = new ArrayList(testData.length);
+ for (final TestData myData: testData) {
+ tasks.add(new Runnable() {
+ public void run() {
+ dateService.jodaIso8601DateParse(myData.iso8601DateString);
+ }
+ });
}
- for (int i = 0; i < LOOP_COUNT; i++)
- assertTrue(completer.take().get());
- printElapsedClockTime("testParseJodaDateInParallel");
+ executeMultiThreadedPerformanceTest(
+ "testParseIso8601DatePerformanceInParallel_JodaAlternative", tasks);
}
}
\ No newline at end of file