package org.simpleframework.http.message; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectableChannel; import java.nio.channels.SocketChannel; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; import org.simpleframework.http.Request; import org.simpleframework.http.Response; import org.simpleframework.http.core.Container; import org.simpleframework.http.core.ContainerSocketProcessor; import org.simpleframework.http.core.ThreadDumper; import org.simpleframework.transport.connect.Connection; import org.simpleframework.transport.connect.SocketConnection; import org.simpleframework.transport.trace.Trace; import org.simpleframework.transport.trace.TraceAnalyzer; public class ContainerPerformanceTest extends TestCase { private static final int ITERATIONS = 100000; private static final int THREADS = 50; private static final byte[] SOURCE_1 = ("GET /index.html HTTP/1.1\r\n"+ "Content-Type: application/x-www-form-urlencoded\r\n"+ "Accept: image/gif;q=1.0,\r\n image/jpeg;q=0.8,\r\n"+ " \t\t image/png;\t\r\n\t"+ " q=1.0,*;q=0.1\r\n"+ "Accept-Language: fr;q=0.1, en-us;q=0.4, en-gb; q=0.8, en;q=0.7\r\n"+ "Host: some.host.com \r\n"+ "Cookie: $Version=1; UID=1234-5678; $Path=/; $Domain=.host.com\r\n"+ "Cookie: $Version=1; NAME=\"Niall Gallagher\"; $path=\"/\"\r\n"+ "\r\n").getBytes(); private static final byte[] SOURCE_2 = ("GET /tmp/amazon_files/21lP7I1XB5L.jpg HTTP/1.1\r\n"+ "Accept-Encoding: gzip, deflate\r\n"+ "Connection: keep-alive\r\n"+ "Referer: http://localhost:9090/tmp/amazon.htm\r\n"+ "Cache-Control: max-age=0\r\n"+ "Host: localhost:9090\r\n"+ "Accept-Language: en-US\r\n"+ "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Version/3.1 Safari/525.13\r\n"+ "Accept: */*\r\n" + "\r\n").getBytes(); private static final byte[] SOURCE_3 = ("GET /tmp/amazon_files/in-your-city-blue-large._V256095983_.gif HTTP/1.1\r\n"+ "Accept-Encoding: gzip, deflate\r\n"+ "Connection: keep-alive\r\n"+ "Referer: http://localhost:9090/tmp/amazon.htm\r\n"+ "Cache-Control: max-age=0\r\n"+ "Host: localhost:9090\r\n"+ "Accept-Language: en-US\r\n"+ "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Version/3.1 Safari/525.13\r\n"+ "Accept: */*\r\n"+ "\r\n").getBytes(); private static final byte[] SOURCE_4 = ("GET /tmp/amazon_files/narrowtimer_transparent._V47062518_.gif HTTP/1.1\r\n"+ "Accept-Encoding: gzip, deflate\r\n"+ "Connection: keep-alive\r\n"+ "Referer: http://localhost:9090/tmp/amazon.htm\r\n"+ "Cache-Control: max-age=0\r\n"+ "Host: localhost:9090\r\n"+ "Accept-Language: en-US\r\n"+ "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Version/3.1 Safari/525.13\r\n"+ "Accept: */*\r\n"+ "\r\n").getBytes(); private static final byte[] CLOSE = ("GET /final_resource.gif HTTP/1.1\r\n"+ "Accept-Encoding: gzip, deflate\r\n"+ "Connection: keep-alive\r\n"+ "Referer: http://localhost:9090/tmp/amazon.htm\r\n"+ "Cache-Control: max-age=0\r\n"+ "Host: localhost:9090\r\n"+ "Accept-Language: en-US\r\n"+ "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Version/3.1 Safari/525.13\r\n"+ "Accept: */*\r\n"+ "Conection: close\r\n"+ "\r\n").getBytes(); private static final byte[] RESPONSE_1 = ("{'product': {\r\n"+ " 'id': '1234',\r\n"+ " 'name': 'AU3TB00001256',\r\n"+ " 'values': {\r\n"+ " 'best': [\r\n"+ " {'bid': '13.344'},\r\n"+ " {'offer': '12.1'},\r\n"+ " {'volume': '100000'}\r\n"+ " ]\r\n"+ " }\r\n"+ "}}").getBytes(); // push through as many valid HTTP/1.1 requests as possible public void testPerformance() throws Exception { final AtomicInteger counter = new AtomicInteger(ITERATIONS * THREADS * 4); final CountDownLatch latch = new CountDownLatch(1); List<Thread> threads = new ArrayList<Thread>(); ThreadDumper dumper = new ThreadDumper(); //TraceAnalyzer analyzer = new DebugTraceAnalyzer(counter, true); TraceAnalyzer analyzer = new DebugTraceAnalyzer(counter, false); CounterContainer container = new CounterContainer(counter, latch); ContainerSocketProcessor processor = new ContainerSocketProcessor(container, 50, 1); Connection connection = new SocketConnection(processor, analyzer); InetSocketAddress address = (InetSocketAddress)connection.connect(null); // ephemeral port Thread.sleep(1000); for(int i = 0; i < THREADS; i++) { final SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", address.getPort())); client.configureBlocking(true); client.finishConnect(); Thread.sleep(10); while(!client.finishConnect()) { Thread.sleep(10); } System.err.println("connected="+client.isConnected()+" blocking="+client.isBlocking()); // read the HTTP/1.1 responses from the TCP stream so it does not fill the TCP window Thread readThread = new Thread() { public void run() { try { byte[] data = new byte[8192]; while(client.isConnected()) { client.read(ByteBuffer.wrap(data)); } }catch(Exception e){ e.printStackTrace(); } } }; // write the HTTP/1.1 requests down the socket for the server to parse and dispatch Thread writeThread = new Thread() { public void run() { try { for(int i = 0; i < ITERATIONS; i++) { client.write(ByteBuffer.wrap(SOURCE_1)); client.write(ByteBuffer.wrap(SOURCE_2)); client.write(ByteBuffer.wrap(SOURCE_3)); client.write(ByteBuffer.wrap(SOURCE_4)); Thread.sleep(1); } client.write(ByteBuffer.wrap(CLOSE)); client.close(); } catch(Exception e){ e.printStackTrace(); } } }; readThread.start(); writeThread.start(); threads.add(readThread); threads.add(writeThread); } dumper.start(); // wait for all clients to finish for(Thread thread : threads){ thread.join(); } latch.await(); connection.close(); dumper.kill(); } // This is a container that counts the callbacks/requests it gets and sends a valid HTTP/1.1 response private class CounterContainer implements Container { private final AtomicInteger counter; private final CountDownLatch latch; private final long start; private final int require; public CounterContainer(AtomicInteger counter, CountDownLatch latch) { this.start = System.currentTimeMillis(); this.require = counter.get(); this.counter = counter; this.latch = latch; } public void handle(Request req, Response resp) { try { OutputStream out = resp.getOutputStream(); String target = req.getPath().getPath(); // parse the HTTP request URI resp.setValue("Content-Type", "application/json"); resp.setValue("Connection", "keep-alive"); resp.setValue("X-Request-URI", target); resp.setContentLength(RESPONSE_1.length); out.write(RESPONSE_1); out.close(); int count = counter.decrementAndGet(); int total = require - count; if(total % 100000 == 0) { long duration = System.currentTimeMillis() - start; DecimalFormat format = new DecimalFormat("###,###,###,###.##"); System.err.println("Request: " + format.format(total) + " in " + format.format(duration) + " which is " + format.format(total / duration) + " per ms and "+format.format(total/(Math.max(duration,1.0)/1000.0))+" per second"); } if(count == 0){ latch.countDown(); } }catch(Exception e){ e.printStackTrace(); } } } // This is just for debugging the I/O if needed public class DebugTraceAnalyzer implements TraceAnalyzer { private final AtomicInteger counter; private final boolean debug; public DebugTraceAnalyzer(AtomicInteger counter, boolean debug){ this.counter = counter; this.debug = debug; } public Trace attach(SelectableChannel channel) { return new DebugTrace(channel); } public void stop() {} private class DebugTrace implements Trace { private final SelectableChannel channel; public DebugTrace(SelectableChannel channel) { this.channel = channel; } public void trace(Object event) { if(debug) { trace(event, ""); } } public void trace(Object event, Object value) { if(debug) { if(value instanceof Throwable) { StringWriter writer = new StringWriter(); PrintWriter out = new PrintWriter(writer); ((Exception)value).printStackTrace(out); out.flush(); value = writer.toString(); } if(value != null && !String.valueOf(value).isEmpty()) { System.err.printf("(%s) %s [%s] %s: %s%n", Thread.currentThread().getName(), channel, counter, event, value); } else { System.err.printf("(%s) %s [%s] %s%n", Thread.currentThread().getName(), channel, counter, event); } } } } } public static void main(String[] list) throws Exception { new ContainerPerformanceTest().testPerformance(); } }