/* * Copyright 2012 Jason Miller * * 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 jj.testing; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static jj.server.Assets.*; import static jj.document.DocumentScriptEnvironment.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.inject.Inject; import jj.App; import jj.ServerRoot; import jj.http.server.EmbeddedHttpRequest; import jj.http.server.EmbeddedHttpServer; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; /** * Started as a scratchpad for a testing API, survives as a basic integration/stress test. If something is * broken, this test usually quits * * @author jason * */ public class BasicServingTest { private static final String INDEX = "/index"; private static final String ANIMAL = "/animal"; public static final List<String> statics; public static final List<String> documents; public static final List<String> stylesheets; public static final List<String> assets; static { List<String> work = Arrays.asList( "/0.txt", "/1.txt", "/2.txt", "/3.txt", "/4.txt", "/5.txt", "/6.txt", "/7.txt", "/8.txt", "/9.txt" ); Collections.shuffle(work); statics = Collections.unmodifiableList(work); documents = Collections.unmodifiableList(Arrays.asList(INDEX, ANIMAL)); stylesheets = Collections.unmodifiableList(Arrays.asList("/test.css", "/style.css")); assets = Collections.unmodifiableList(Arrays.asList("/" + JQUERY_JS, "/" + JJ_JS, "/" + FAVICON_ICO)); } @Rule public JibbrJabbrTestServer app = new JibbrJabbrTestServer(ServerRoot.one, App.app1) .verifying() //.recording() .injectInstance(this); @Inject EmbeddedHttpServer server; @Inject TraceModeSwitch trace; private void runBasicStressTest(final int timeout, final int totalClients, final List<String> uris) throws Throwable { final AssertionError error = new AssertionError("failure!"); final Latch latch = new Latch(totalClients); for (int i = 0; i < totalClients; ++i) { String uri = uris.get(i % uris.size()); server.request(new EmbeddedHttpRequest(uri), response -> { try { trace.mode().traceEvent(uri, response.bodyContentAsBytes()); } catch (Throwable t) { error.addSuppressed(t); } finally { latch.countDown(); } }); } AssertionError timeoutError = null; try { latch.await(timeout, SECONDS); } catch (AssertionError ae) { timeoutError = ae; } if (error.getSuppressed().length > 0) { throw error; } if (timeoutError != null) { throw timeoutError; } } //@Test public void runDocumentTest() throws Throwable { runBasicStressTest(2, 40, documents); } //@Test public void runStaticTest() throws Throwable { runBasicStressTest(2, 400, statics); } //@Test public void runCssTest() throws Throwable { runBasicStressTest(2, 400, stylesheets); } //@Test public void runAssetTest() throws Throwable { runBasicStressTest(3, 400, assets); } private List<String> makeAll() { List<String> requests = new ArrayList<>(); requests.addAll(documents); requests.addAll(statics); requests.addAll(stylesheets); requests.addAll(assets); Collections.shuffle(requests); return requests; } @Test public void runMixedTest() throws Exception { timePoundIt(16, 5, 100); } @Ignore // until there is a cheaper method of comparison with expected responses. // the current comparison is biasing the result //@Test public void areYouKiddingMe() throws Exception { final int threadCount = 12; // one quiet one to warm things up poundIt(threadCount, 5, 4000); // and now make them loud timePoundIt(threadCount, 5, 5000); System.out.println(); timePoundIt(threadCount, 5, 500); System.out.println(); timePoundIt(threadCount, 5, 5000); } private void poundIt(final int threadCount, final int perClientTimeout, final int perClientRequestCount) throws Exception { final List<String> requests = makeAll(); final Latch latch = new Latch(threadCount); final ExecutorService service = Executors.newFixedThreadPool(threadCount); final Throwable[] throwables = new Throwable[threadCount]; try { for (int i = 0; i < threadCount; ++i) { final int index = i; service.submit(() -> { try { runBasicStressTest(perClientTimeout, perClientRequestCount, requests); } catch (Throwable t) { throwables[index] = t; } finally { latch.countDown(); } }); } latch.await(1, MINUTES); } finally { service.shutdownNow(); } boolean fail = false; AssertionError error = new AssertionError("pounded it into submission"); for (Throwable t : throwables) { if (t != null) { error.addSuppressed(t); fail = true; } } if (fail) throw error; } private void timePoundIt(final int threadCount, final int perClientTimeout, final int perClientRequestCount) throws Exception { final long startingTotalMemory = Runtime.getRuntime().totalMemory(); final long startingMaxMemory = Runtime.getRuntime().maxMemory(); final long startingFreeMemory = Runtime.getRuntime().freeMemory(); final long start = System.currentTimeMillis(); poundIt(perClientTimeout, threadCount, perClientRequestCount); int total = (perClientRequestCount * threadCount); long time = (System.currentTimeMillis() - start); System.out.println("loaded " + total + " responses in " + time + " milliseconds."); System.out.println("free\t" + startingFreeMemory + "\ttotal\t" + startingTotalMemory + "\tmax\t" + startingMaxMemory + " at start"); System.out.println("free\t" + Runtime.getRuntime().freeMemory() + "\ttotal\t" + Runtime.getRuntime().totalMemory() + "\tmax\t" + Runtime.getRuntime().maxMemory() + " at finish"); Runtime.getRuntime().gc(); System.out.println("free\t" + Runtime.getRuntime().freeMemory() + "\ttotal\t" + Runtime.getRuntime().totalMemory() + "\tmax\t" + Runtime.getRuntime().maxMemory() + " after GC"); } }