com.betfair.application.performance.BaselinePerformanceTester.java Source code

Java tutorial

Introduction

Here is the source code for com.betfair.application.performance.BaselinePerformanceTester.java

Source

/*
 * Copyright 2013, The Sporting Exchange Limited
 *
 * 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 com.betfair.application.performance;

import com.betfair.application.util.BaselineClientConstants;
import com.betfair.application.util.HttpBodyBuilder;
import com.betfair.application.util.HttpCallLogEntry;
import com.betfair.application.util.HttpCallable;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

public class BaselinePerformanceTester implements BaselineClientConstants {

    // REQUEST MODELLING
    private static final int SOAP_COUNT;
    private static final int REST_XML_COUNT;
    private static final int REST_JSON_COUNT;

    private static final int SIMPLE_GET;
    private static final int LARGE_GET;
    private static final int COMPLEX_MUTATOR;
    private static final int LARGE_POST;
    private static final int EXCEPTION;
    private static final int STYLES;
    private static final int GET_TIMEOUT;
    private static final int DATES;
    private static final int MAPS;
    private static int TOTAL_CALL_NUMBER;

    private static final int NUM_THREADS;
    private static final int NUM_CALLS;
    private static final int TRACE_EVERY;
    private static final String HOST;
    private static final int PORT;
    private static final String SOAP_ENDPOINT;
    private static final String REST_BASE;

    private static final boolean CHECK_LOG;
    private static final Map<String, String> METHOD_NAMES = new HashMap<String, String>();

    static {
        // Read in the properties.
        try {
            Properties props = new Properties();
            InputStream is = BaselinePerformanceTester.class.getResourceAsStream("/perftester/perftest.properties");
            props.load(is);
            is.close();
            HOST = props.getProperty("HOST");
            PORT = Integer.parseInt(props.getProperty("PORT"));
            String nullPart = "";
            if (Boolean.valueOf(props.getProperty("NULLTEST"))) {
                nullPart = "/null";
            }
            SOAP_ENDPOINT = "http://" + HOST + ":" + PORT + nullPart + "/BaselineService/v2.0";
            REST_BASE = "http://" + HOST + ":" + PORT + nullPart + "/baseline/v2.0/";

            NUM_THREADS = Integer.parseInt(props.getProperty("NUM_THREADS"));
            NUM_CALLS = Integer.parseInt(props.getProperty("NUM_CALLS"));
            TRACE_EVERY = Integer.parseInt(props.getProperty("TRACE_EVERY"));

            SOAP_COUNT = Integer.parseInt(props.getProperty(SOAP));
            REST_XML_COUNT = Integer.parseInt(props.getProperty("REST_XML"));
            REST_JSON_COUNT = Integer.parseInt(props.getProperty("REST_JSON"));

            SIMPLE_GET = Integer.parseInt(props.getProperty("SIMPLE_GET"));
            TOTAL_CALL_NUMBER += SIMPLE_GET;
            METHOD_NAMES.put("SIMPLE_GET", "testSimpleGet");
            LARGE_GET = Integer.parseInt(props.getProperty("LARGE_GET"));
            TOTAL_CALL_NUMBER += LARGE_GET;
            METHOD_NAMES.put("LARGE_GET", "testLargeGet");
            COMPLEX_MUTATOR = Integer.parseInt(props.getProperty("COMPLEX_MUTATOR"));
            TOTAL_CALL_NUMBER += COMPLEX_MUTATOR;
            METHOD_NAMES.put("COMPLEX_MUTATOR", "testComplexMutator");
            LARGE_POST = Integer.parseInt(props.getProperty("LARGE_POST"));
            TOTAL_CALL_NUMBER += LARGE_POST;
            METHOD_NAMES.put("LARGE_POST", "testLargePost");
            EXCEPTION = Integer.parseInt(props.getProperty("EXCEPTION"));
            TOTAL_CALL_NUMBER += EXCEPTION;
            METHOD_NAMES.put("EXCEPTION", "testException");
            STYLES = Integer.parseInt(props.getProperty("STYLES"));
            TOTAL_CALL_NUMBER += STYLES;
            METHOD_NAMES.put("STYLES", "testParameterStyles");
            GET_TIMEOUT = Integer.parseInt(props.getProperty("GET_TIMEOUT"));
            TOTAL_CALL_NUMBER += GET_TIMEOUT;
            METHOD_NAMES.put("GET_TIMEOUT", "testGetTimeout");
            DATES = Integer.parseInt(props.getProperty("DATES"));
            TOTAL_CALL_NUMBER += DATES;
            METHOD_NAMES.put("DATES", "testDateRetrieval");
            MAPS = Integer.parseInt(props.getProperty("MAPS"));
            TOTAL_CALL_NUMBER += MAPS;
            METHOD_NAMES.put("MAPS", "testMapRetrieval");

            CHECK_LOG = Boolean.valueOf(props.getProperty("CHECK_LOG"));
        } catch (Exception e) {
            throw new RuntimeException("Failed to initialise tester", e);
        }
    }

    private static String CHARSET = "utf-8";

    private static final Map<HttpCallLogEntry, CallLogCount> LOG = new TreeMap<HttpCallLogEntry, CallLogCount>();

    private static final ExecutorService executor = (ExecutorService) Executors.newFixedThreadPool(NUM_THREADS);
    private static ThreadLocal<HttpClient> client = new ThreadLocal<HttpClient>();

    private static RangeFinder<HttpCallable> requests = new RangeFinder<HttpCallable>();
    private static RangeFinder<String> protocols = new RangeFinder<String>();

    private static AtomicLong callsRemaining = new AtomicLong(0);
    private static AtomicLong callsMade = new AtomicLong(0);
    private static AtomicLong avgRandomiser = new AtomicLong(0);
    private static AtomicLong bytesReceived = new AtomicLong(0);
    private static AtomicLong totalLogRetries = new AtomicLong(0);

    private static void setup() {
        requests.addValue(SIMPLE_GET, new HttpCallable("SIMPLE_GET", REST_BASE + SIMPLE_GET_PATH, SOAP_ENDPOINT,
                HttpStatus.SC_OK, new HttpBodyBuilder(SIMPLE_GET_SOAP)));
        requests.addValue(LARGE_GET, new HttpCallable("LARGE_GET", REST_BASE + LARGE_GET_PATH, SOAP_ENDPOINT,
                HttpStatus.SC_OK, new HttpBodyBuilder(LARGE_GET_SOAP)));
        requests.addValue(GET_TIMEOUT, new HttpCallable("TIMEOUT", REST_BASE + SIMPLE_TIMEOUT_PATH, SOAP_ENDPOINT,
                HttpStatus.SC_OK, new HttpBodyBuilder(TIMEOUT_SOAP)));
        requests.addValue(EXCEPTION, new HttpCallable("EXCEPTION", REST_BASE + EXCEPTION_PATH_UNAUTHORISED,
                SOAP_ENDPOINT, HttpStatus.SC_UNAUTHORIZED, new HttpBodyBuilder(EXC_GET_SOAP)));
        requests.addValue(STYLES, new HttpCallable("STYLES", REST_BASE + STYLES_PATH, SOAP_ENDPOINT,
                HttpStatus.SC_OK, new HttpBodyBuilder(STYLES_SOAP)));

        requests.addValue(DATES,
                new HttpCallable("DATES", REST_BASE + "dates", SOAP_ENDPOINT, new HttpBodyBuilder(DATES_BODY_JSON),
                        new HttpBodyBuilder(DATES_BODY_XML), new HttpBodyBuilder(DATES_SOAP)));

        requests.addValue(COMPLEX_MUTATOR,
                new HttpCallable("COMPLEX_MUTATOR", REST_BASE + "complex", SOAP_ENDPOINT,
                        new HttpBodyBuilder(COMPLEX_MUTATOR_BODY_JSON),
                        new HttpBodyBuilder(COMPLEX_MUTATOR_BODY_XML), new HttpBodyBuilder(COMPLEX_MUTATOR_SOAP)));

        requests.addValue(LARGE_POST, new HttpCallable("LARGE_POST", REST_BASE + "large", SOAP_ENDPOINT,
                new HttpBodyBuilder(LARGE_POST_BODY_JSON_START, LARGE_POST_BODY_JSON_REPEAT,
                        LARGE_POST_BODY_JSON_SEPARATOR, LARGE_POST_BODY_JSON_END),
                new HttpBodyBuilder(LARGE_POST_BODY_XML_START, LARGE_POST_BODY_XML_REPEAT, "",
                        LARGE_POST_BODY_XML_END),
                new HttpBodyBuilder(LARGE_POST_SOAP_START, LARGE_POST_SOAP_REPEAT, "", LARGE_POST_SOAP_END)));

        requests.addValue(MAPS,
                new HttpCallable("MAPS", REST_BASE + "map1", SOAP_ENDPOINT, new HttpBodyBuilder(MAPS_BODY_JSON),
                        new HttpBodyBuilder(MAPS_BODY_XML), new HttpBodyBuilder(MAPS_SOAP)));

        protocols.addValue(SOAP_COUNT, SOAP);
        protocols.addValue(SOAP_COUNT, SOAP);
        protocols.addValue(REST_XML_COUNT, APPLICATION_XML);
        protocols.addValue(REST_XML_COUNT, TEXT_XML);
        protocols.addValue(REST_JSON_COUNT, APPLICATION_JSON);
        protocols.addValue(REST_JSON_COUNT, TEXT_JSON);

    }

    public static void main(String[] args) {
        setup();
        // Create an instance of HttpClient.
        Long time = System.currentTimeMillis();
        final Random rnd = new Random(1);
        for (int i = 0; i < NUM_CALLS; i++) {
            callsRemaining.incrementAndGet();
            executor.execute(new Runnable() {
                public void run() {
                    makeRequest(getRequest(rnd), getContentType(rnd), rnd);
                }
            });
        }

        long lastTime = callsRemaining.longValue();
        while (callsRemaining.longValue() > 0) {
            try {
                Thread.sleep(1000);
            } catch (Exception ignored) {
            }
            if (lastTime - 1000 > callsRemaining.longValue()) {
                lastTime = callsRemaining.get();
                System.out.print(".");
            }
        }
        time = System.currentTimeMillis() - time;
        System.out.println("Done.");

        executor.shutdown();

        analyseCalls(time);
    }

    private static HttpCallable getRequest(Random rnd) {
        return requests.getValue(rnd.nextInt(requests.getMaxRange()));
    }

    private static String getContentType(Random rnd) {
        return protocols.getValue(rnd.nextInt(protocols.getMaxRange()));
    }

    private static void analyseCalls(long time) {
        Map<String, Map<HttpCallLogEntry, CallLogCount>> maps = new TreeMap<String, Map<HttpCallLogEntry, CallLogCount>>();
        maps.put("SOAP      ", new HashMap<HttpCallLogEntry, CallLogCount>());
        maps.put("REST_XML  ", new HashMap<HttpCallLogEntry, CallLogCount>());
        maps.put("REST_JSON ", new HashMap<HttpCallLogEntry, CallLogCount>());

        long totalCalls = 0;
        long totalCallTime = 0;
        for (Map.Entry<HttpCallLogEntry, CallLogCount> entry : LOG.entrySet()) {
            HttpCallLogEntry e = entry.getKey();
            CallLogCount c = entry.getValue();
            double callTimeMilli = c.aveCallTime() / 1000000d;
            System.out.format("%16s called %6d times with %2d failures in average time of %8.3f ms (%16s)\n",
                    e.getMethod(), c.numCalls.get(), c.failures.get(), callTimeMilli, e.getProtocol());
            totalCalls += c.numCalls.get();
            totalCallTime += c.callTime.get();

            if (e.getProtocol().equals(SOAP))
                maps.get("SOAP      ").put(e, c);
            if (e.getProtocol().endsWith("xml"))
                maps.get("REST_XML  ").put(e, c);
            if (e.getProtocol().endsWith("json"))
                maps.get("REST_JSON ").put(e, c);
        }
        System.out.println();
        for (Map.Entry<String, Map<HttpCallLogEntry, CallLogCount>> me : maps.entrySet()) {
            simpleSummary(me.getKey(), me.getValue());
        }
        System.out.println();
        System.out.format("Average randomiser    : %d\n", avgRandomiser.longValue() / totalCalls);
        System.out.format("Average bytes/msg     : %d\n", bytesReceived.longValue() / totalCalls);
        System.out.format("HTTP Threads          : %d\n", NUM_THREADS);
        System.out.format("Total Log Retries     : %d\n", totalLogRetries.longValue());
        System.out.format("Total Calls made      : %d\n", totalCalls);
        System.out.format("Average time per call : %3.3f ms\n", totalCallTime / (totalCalls * 1000000d));
        System.out.format("Total Time taken      : %3.3f seconds\n", time / 1000.0d);
        System.out.format("TPS                   : %d\n", (int) (totalCalls / (time / 1000.0d)));

    }

    public static void simpleSummary(String ident, Map<HttpCallLogEntry, CallLogCount> calls) {
        if (calls.isEmpty())
            return;

        long totalCalls = 0;
        long totalCallTime = 0;
        for (Map.Entry<HttpCallLogEntry, CallLogCount> entry : calls.entrySet()) {
            CallLogCount c = entry.getValue();
            totalCalls += c.numCalls.get();
            totalCallTime += c.callTime.get();
        }
        System.out.format("%20s: Total Calls: %6d, Ave time per call: %3.3f ms\n", ident, totalCalls,
                totalCallTime / (totalCalls * 1000000d));

    }

    private static void makeRequest(HttpCallable call, String contentType, Random rnd) {
        HttpClient httpc = client.get();
        if (httpc == null) {
            httpc = new DefaultHttpClient();
            client.set(httpc);
        }
        HttpCallLogEntry cle = new HttpCallLogEntry();
        int randomiser = rnd.nextInt(50);
        avgRandomiser.addAndGet(randomiser);
        HttpUriRequest method = call.getMethod(contentType, new Object[] { randomiser }, randomiser, cle);
        method.addHeader("Authority", "CoUGARUK");
        CallLogCount loggedCount = null;
        // Execute the method.
        synchronized (LOG) {
            loggedCount = LOG.get(cle);
            if (loggedCount == null) {
                loggedCount = new CallLogCount();
                LOG.put(cle, loggedCount);
            }
        }
        long nanoTime = System.nanoTime();

        InputStream is = null;

        try {

            if (TRACE_EVERY > 0 && callsMade.incrementAndGet() % TRACE_EVERY == 0) {
                method.addHeader("X-Trace-Me", "true");
            }
            loggedCount.numCalls.incrementAndGet();
            final HttpResponse httpResponse = httpc.execute(method);
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            boolean failed = false;

            int expectedHTTPCode = call.expectedResult();
            if (contentType.equals(SOAP) && expectedHTTPCode != HttpStatus.SC_OK) {
                // All SOAP errors are 500.
                expectedHTTPCode = HttpStatus.SC_INTERNAL_SERVER_ERROR;
            }
            if (statusCode != expectedHTTPCode) {
                System.err.println("Method failed: " + httpResponse.getStatusLine().getReasonPhrase());
                failed = true;
            }
            // Read the response body.
            is = httpResponse.getEntity().getContent();
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b;
            while ((b = is.read()) != -1) {
                baos.write(b);
            }
            is.close();
            String result = new String(baos.toByteArray(), CHARSET);
            if (result.length() == 0) {
                System.err.println("FAILURE: Empty buffer returned");
                failed = true;
            }
            if (failed) {
                loggedCount.failures.incrementAndGet();
            }
            bytesReceived.addAndGet(baos.toByteArray().length);

            if (CHECK_LOG && NUM_THREADS == 1) {
                File logFile = new File(
                        "C:\\perforce\\se\\development\\HEAD\\cougar\\cougar-framework\\baseline\\baseline-launch\\logs\\request-Baseline.log");
                String lastLine = getLastLine(logFile);
                int tries = 0;
                while (!lastLine.contains(METHOD_NAMES.get(call.getName()))) {
                    if (++tries > 5) {
                        System.err.println(
                                "LOG FAIL: Call: " + METHOD_NAMES.get(call.getName()) + ", Line: " + lastLine);
                    } else {
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                        }
                    }
                    lastLine = getLastLine(logFile);
                }
                totalLogRetries.addAndGet(tries);
            }

        } catch (Exception e) {
            System.err.println("Fatal transport error: " + e.getMessage());
            e.printStackTrace();
            loggedCount.failures.incrementAndGet();
        } finally {
            // Release the connection.
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    /* ignore */}
            }
            callsRemaining.decrementAndGet();

            nanoTime = System.nanoTime() - nanoTime;
            loggedCount.callTime.addAndGet(nanoTime);
        }
    }

    private static String getLastLine(File file) throws IOException {

        InputStreamReader streamReader = new InputStreamReader(new FileInputStream(file));

        BufferedReader br = new BufferedReader(streamReader);

        String line = null;
        while (br.ready()) {
            line = br.readLine();
        }
        return line;
    }

    private static class CallLogCount {
        private AtomicLong numCalls = new AtomicLong(0);
        private AtomicLong failures = new AtomicLong(0);
        private AtomicLong callTime = new AtomicLong(0);

        private long aveCallTime() {
            if (numCalls.longValue() == 0)
                return 0;
            return callTime.longValue() / (numCalls.longValue());
        }
    }

    private static class RangeFinder<T> {
        TreeMap<Integer, T> map = new TreeMap<Integer, T>();
        int rangeVal = 0;

        public void addValue(int range, T t) {
            if (range == 0)
                return;
            rangeVal += range;
            map.put(rangeVal, t);
        }

        public int getMaxRange() {
            return rangeVal;
        }

        public T getValue(int point) {
            if (point < 0 || point >= rangeVal) {
                throw new IllegalArgumentException();
            }
            for (Map.Entry<Integer, T> entry : map.entrySet()) {
                if (point < entry.getKey()) {
                    return entry.getValue();
                }
            }
            throw new IllegalStateException("Tits");
        }
    }
}