
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;

class SelectAttachment {
    private static final Random r = new Random();
    // private static final String[] urls = { "/d/aarp", "/d/about", "/d/zoo",
    // "/d/throw",
    // "/d/new", "/tmpls.js", "/mustache.js" };

    private static final String[] urls = { "/", "/css/landing.css", "/imgs/l/scott.png" };

    String uri;
    ByteBuffer request;
    int response_length = -1;
    int response_cnt = -1;

    public SelectAttachment(String uri) {
        this.uri = uri;
        request = ByteBuffer.wrap(("GET " + uri + " HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
                .getBytes());
    }

    public static SelectAttachment next() {
        return new SelectAttachment(urls[r.nextInt(urls.length)]);
    }
}

public class PerformanceBench {

    static final boolean DEBUG = false;

    static final byte CR = 13;
    static final byte LF = 10;
    static final String CL = "content-length: ";

    public static String readLine(ByteBuffer buffer) {
        StringBuilder sb = new StringBuilder(64);
        char b;
        loop: for (;;) {
            b = (char) buffer.get();
            switch (b) {
            case CR:
                if (buffer.get() == LF)
                    break loop;
                break;
            case LF:
                break loop;
            }
            sb.append(b);
        }
        return sb.toString();
    }

    private static void D(String mesg) {
        if (DEBUG) {
            System.out.println(mesg);
        }
    }

    final static int concurrency = 4000;
    final static int total = 2000000;
    static int remaining = total;
    static long totalByteReceive = 0;

    static InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 4348);
    static ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024 * 64);

    static long start = System.currentTimeMillis();

    public static void main(String[] args) throws IOException, InterruptedException {

        System.out.println("concurrency: " + concurrency + "; total requests: " + total);

        Selector selector = Selector.open();

        for (int i = 0; i < concurrency; ++i) {
            if (i % 100 == 0) {
                Thread.sleep(10);
            }
            connect(addr, selector);
        }

        start = System.currentTimeMillis();

        while (true) {
            int select = selector.select();
            D("selec return " + select);
            if (select > 0) {
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectedKeys.iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    if (key.isConnectable()) {
                        SocketChannel ch = (SocketChannel) key.channel();
                        try {
                            if (ch.finishConnect()) {
                                key.interestOps(SelectionKey.OP_WRITE);
                            }
                        } catch (Exception e) {
                            ch.close();
                            e.printStackTrace();
                            D(e.getMessage() + "\t" + "close and reconnect");
                            connect(addr, selector); // reconnect
                        }
                    } else if (key.isWritable()) {
                        doWrite(key);
                    } else if (key.isReadable()) {
                        doRead(selector, key);
                    }
                }
                selectedKeys.clear();
            }
        }
    }

    private static void doWrite(SelectionKey key) throws IOException {
        SocketChannel ch = (SocketChannel) key.channel();
        SelectAttachment att = (SelectAttachment) key.attachment();
        ByteBuffer buffer = att.request;
        ch.write(buffer);
        if (!buffer.hasRemaining()) {
            key.interestOps(SelectionKey.OP_READ);
        }
    }

    private static void doRead(Selector selector, SelectionKey key) throws IOException,
            SocketException, ClosedChannelException {
        SocketChannel ch = (SocketChannel) key.channel();
        SelectAttachment att = (SelectAttachment) key.attachment();
        readBuffer.clear();
        while (true) {
            int read = ch.read(readBuffer);
            if (read == -1) {
                ch.close();
                decAndPrint();
                D("remote closed, connecton new, remaining: " + remaining);
                connect(addr, selector);
                break;
            } else if (read == 0) {
                D("read zero");
                break;
            } else {
                totalByteReceive += read;
                if (att.response_length == -1) {
                    readBuffer.flip();
                    String line = readLine(readBuffer);
                    while (line.length() > 0) {
                        line = line.toLowerCase();
                        if (line.startsWith(CL)) {
                            String length = line.substring(CL.length());
                            att.response_length = Integer.valueOf(length);
                            att.response_cnt = att.response_length;
                        }
                        line = readLine(readBuffer);
                    }
                    att.response_cnt -= readBuffer.remaining();
                } else {
                    att.response_cnt -= read;
                }
                if (att.response_cnt == 0) {
                    D("read all");
                    decAndPrint();
                    key.attach(SelectAttachment.next());
                    key.interestOps(SelectionKey.OP_WRITE);
                }
            }
        }
    }

    private static void decAndPrint() {
        remaining -= 1;
        if (remaining % (total / 10) == 0) {
            System.out.println("remaining\t" + remaining);
        }

        if (remaining == 0) {
            long time = (System.currentTimeMillis() - start);
            double receiveM = totalByteReceive / 1024.0 / 1024;
            double reqs = (double) total / time * 1000;
            double ms = (double) receiveM / time * 1000;

            System.out
                    .printf("concurrency: %d; requests: %d; time: %dms; req/s: %.2f; receive bytes: %.2fM data; throughput: %.2f M/s\n",
                            concurrency, total, time, reqs, receiveM, ms);
            System.exit(0);
        }
    }

    private static void connect(InetSocketAddress addr, Selector selector) throws IOException,
            SocketException, ClosedChannelException {
        SocketChannel ch = SocketChannel.open();
        ch.socket().setReuseAddress(true);
        ch.configureBlocking(false);
        ch.register(selector, SelectionKey.OP_CONNECT, SelectAttachment.next());
        ch.connect(addr);
    }
}
