com.aliyun.oss.perftests.PerftestRunner.java Source code

Java tutorial

Introduction

Here is the source code for com.aliyun.oss.perftests.PerftestRunner.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.aliyun.oss.perftests;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

import junit.framework.Assert;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;

import com.aliyun.oss.perftests.TestScenario.Type;

public class PerftestRunner {

    private static final Log log = LogFactory.getLog(PerftestRunner.class);

    private TestScenario scenario;
    private OSSClient ossClient;
    private Date startTime;
    private Date endTime;
    private int putErrNum = 0;
    private int getErrNum = 0;
    private final ReentrantLock lock = new ReentrantLock();
    private static String fileName = "perftest.txt";

    private byte[] byteArray1KB;
    private byte[] byteArray100KB;
    private byte[] byteArray1MB;
    private byte[] byteArray4MB;

    private static TestScenario.Type determineScenarioType(final String scenarioTypeString) {
        TestScenario.Type t = Type.UNKNOWN;
        if (scenarioTypeString.equals("onlyput-1MB")) {
            t = Type.ONLY_PUT_1MB;
        } else if (scenarioTypeString.equals("onlyput-4MB")) {
            t = Type.ONLY_PUT_4MB;
        } else if (scenarioTypeString.equals("get-and-put-1vs1-1KB")) {
            t = Type.GET_AND_PUT_1VS1_1KB;
        } else if (scenarioTypeString.equals("get-and-put-1vs1-100KB")) {
            t = Type.GET_AND_PUT_1VS1_100KB;
        } else if (scenarioTypeString.equals("get-and-put-1vs1-1MB")) {
            t = Type.GET_AND_PUT_1VS1_1MB;
        } else if (scenarioTypeString.equals("get-and-put-1vs1-4MB")) {
            t = Type.GET_AND_PUT_1VS1_4MB;
        } else if (scenarioTypeString.equals("get-and-put-1vs1-1MB")) {
            t = Type.GET_AND_PUT_1VS1_1MB;
        } else if (scenarioTypeString.equals("get-and-put-1vs4-1MB")) {
            t = Type.GET_AND_PUT_1VS4_1MB;
        } else if (scenarioTypeString.equals("get-and-put-1vs4-4MB")) {
            t = Type.GET_AND_PUT_1VS4_4MB;
        } else {
            throw new IllegalArgumentException("Unsupported test sconario type " + scenarioTypeString);
        }

        return t;
    }

    public void buildScenario(final String scenarioTypeString) {
        File confFile = new File(System.getProperty("user.dir") + File.separator + "runner_conf.xml");
        InputStream input = null;
        try {
            input = new FileInputStream(confFile);
        } catch (FileNotFoundException e) {
            log.error(e);
            Assert.fail(e.getMessage());
        }
        SAXBuilder builder = new SAXBuilder();
        try {
            Document doc = builder.build(input);
            Element root = doc.getRootElement();
            scenario = new TestScenario();
            scenario.setHost(root.getChildText("host"));
            scenario.setAccessId(root.getChildText("accessid"));
            scenario.setAccessKey(root.getChildText("accesskey"));
            scenario.setBucketName(root.getChildText("bucket"));

            scenario.setType(determineScenarioType(scenarioTypeString));

            Element target = root.getChild(scenarioTypeString);
            if (target != null) {
                scenario.setContentLength(Long.parseLong(target.getChildText("size")));
                scenario.setPutThreadNumber(Integer.parseInt(target.getChildText("putthread")));
                scenario.setGetThreadNumber(Integer.parseInt(target.getChildText("getthread")));
                scenario.setDurationInSeconds(Integer.parseInt(target.getChildText("time")));
                scenario.setGetQPS(Integer.parseInt(target.getChildText("getqps")));
                scenario.setPutQPS(Integer.parseInt(target.getChildText("putqps")));
            } else {
                log.error("Unable to locate XML element " + scenarioTypeString);
                Assert.fail("Unable to locate XML element " + scenarioTypeString);
            }
        } catch (JDOMException e) {
            e.printStackTrace();
            Assert.fail(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            Assert.fail(e.getMessage());
        }
    }

    public void prepareEnv() {
        String endpoint = scenario.getHost();
        String accessId = scenario.getAccessId();
        String accessKey = scenario.getAccessKey();
        ossClient = new OSSClient(endpoint, accessId, accessKey);

        try {
            byteArray1KB = createFixedLengthBytes(1024);
            byteArray100KB = createFixedLengthBytes(102400);
            byteArray1MB = createFixedLengthBytes(1 * 1024 * 1024);
            byteArray4MB = createFixedLengthBytes(4 * 1024 * 1024);
        } catch (Exception e) {
            log.error(e.getMessage());
            Assert.fail(e.getMessage());
        }
    }

    public void createBucket() {
        String bucketName = scenario.getBucketName();
        try {
            ossClient.createBucket(bucketName);
        } catch (OSSException e) {
            log.error("Put bucket " + bucketName + " error " + e.getMessage());
            Assert.fail(e.getMessage());
        } catch (ClientException e) {
            log.error("Put bucket " + bucketName + " error " + e.getMessage());
            Assert.fail(e.getMessage());
        } catch (Exception e) {
            log.error("Put bucket " + bucketName + " error " + e.getMessage());
            Assert.fail(e.getMessage());
        }
    }

    private static byte[] createFixedLengthBytes(int fixedLength) {
        byte[] data = new byte[fixedLength];
        for (int i = 0; i < fixedLength; i++) {
            data[i] = 'a';
        }
        return data;
    }

    private byte[] chooseByteArray(long contentLength) {
        if (contentLength == 1024) {
            return byteArray1KB;
        } else if (contentLength == 102400) {
            return byteArray100KB;
        } else if (contentLength == 1 * 1024 * 1024) {
            return byteArray1MB;
        } else if (contentLength == 4 * 1024 * 1024) {
            return byteArray4MB;
        } else {
            throw new IllegalArgumentException("Illegal content length, only support 1KB & 100KB & 1MB & 4MB");
        }
    }

    private boolean hasExpired() {
        Date now = new Date();
        long interval = (now.getTime() - startTime.getTime()) / 1000;
        return interval > scenario.getDurationInSeconds();
    }

    private String buildObjectKey(String keyPrefix) {
        int n = new Random().nextInt(100000);
        StringBuilder sb = new StringBuilder();
        sb.append(keyPrefix);
        sb.append(Integer.toString(n));
        return sb.toString();
    }

    private static Thread[] joinThreads(Thread[]... threadArrays) {
        List<Thread> threadList = new ArrayList<Thread>();
        for (Thread[] tarr : threadArrays) {
            for (Thread t : tarr) {
                threadList.add(t);
            }
        }
        Thread[] joined = new Thread[threadList.size()];
        return threadList.toArray(joined);
    }

    public static void waitAll(Thread[] targets) {
        for (int i = 0; i < targets.length; i++) {
            targets[i].start();
        }

        for (int i = 0; i < targets.length; i++) {
            try {
                targets[i].join();
            } catch (InterruptedException e) {
            }
        }
    }

    private String getTestName(OperationType type) {
        switch (type) {
        case PUT:
            return "PutObject";
        case GET:
            return "GetObject";
        default:
            return "UnknownTest";
        }
    }

    private String getLengthString(long contentLength) {
        if (contentLength > 0) {
            if (contentLength % (1024 * 1024 * 1024) == 0) {
                return "" + contentLength / (1024 * 1024 * 1024) + "G";
            } else if (contentLength % (1024 * 1024) == 0) {
                return "" + contentLength / (1024 * 1024) + "M";
            } else if (contentLength % 1024 == 0) {
                return "" + contentLength / 1024 + "K";
            }
        }
        return "" + contentLength + "B";
    }

    private String getTimeSpanString(long passTime) {
        if (passTime > 0) {
            if (passTime % 3600 == 0) {
                return "" + passTime / 3600 + "hour";
            } else if (passTime % 60 == 0) {
                return "" + passTime / 60 + "min";
            }
        }
        return "" + passTime + "s";
    }

    private void calculateResult(final List<Long> latencyArray, OperationType type, int threadNum) {
        try {
            int errNum = type == OperationType.PUT ? putErrNum : getErrNum;
            int totalNum = latencyArray.size() + errNum;
            if (totalNum <= 0) {
                return;
            }

            long thresholds[] = { 10, 50, 100, 1000, 3000, Long.MAX_VALUE };
            long thresholdNum[] = new long[6];
            long avgLatency = 0;
            Collections.sort(latencyArray);
            int j = 0;
            for (int i = 0; i < thresholds.length; i++) {
                for (; j < latencyArray.size() && latencyArray.get(j) <= thresholds[i]; j++) {
                    thresholdNum[i]++;
                    avgLatency += latencyArray.get(j);
                }
            }
            long passTime = endTime.getTime() - startTime.getTime();
            double qps = latencyArray.size() * 1000.0 / passTime;
            double errRate = errNum * 1.0 / totalNum;
            avgLatency = avgLatency / latencyArray.size();
            double throughput = qps * scenario.getContentLength() / 1048576;
            String contentStr = null;
            contentStr = String.format("TestType:%s\n", getTestName(type));
            contentStr += String.format("threads:%d, %s, %s\n", threadNum,
                    getLengthString(scenario.getContentLength()),
                    getTimeSpanString(scenario.getDurationInSeconds()));
            contentStr += String.format("Qps:%.1f Throughput:%.2fM AvgLatency:%dms\n", qps, throughput, avgLatency);
            contentStr += String.format("ErrorRate:%.2f\n", errRate);
            contentStr += "Latency Range:\n";
            contentStr += String.format("%d-%dms\t%.2f%s\n", 0, thresholds[0], thresholdNum[0] * 100.0 / totalNum,
                    "%");
            for (int i = 0; i < thresholds.length - 2; i++) {
                contentStr += String.format("%d-%dms\t%.2f%s\n", thresholds[i], thresholds[i + 1],
                        thresholdNum[i + 1] * 100.0 / totalNum, "%");
            }
            contentStr += String.format("%dms+\t\t%.2f%s\n", thresholds[thresholds.length - 2],
                    thresholdNum[thresholdNum.length - 1] * 100.0 / totalNum, "%");
            //print result
            System.out.printf("%s", contentStr);
            // write file
            writeResult(contentStr);
        } catch (Exception e) {
            log.error("Unexpected exception occurs when calculate result " + getTestName(type));
            Assert.fail(e.getMessage());
        }
    }

    public void writeResult(final String contentStr) {
        FileWriter writer = null;
        try {
            writer = new FileWriter(fileName, true);
            writer.write(contentStr);
        } catch (IOException e) {
            // TODO: handle exception
            log.error(e.getMessage());
            Assert.fail(e.getMessage());
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (Exception e2) {
                // TODO: handle exception
                log.error(e2.getMessage());
                Assert.fail(e2.getMessage());
            }
        }
    }

    private void recordError(OperationType type) {
        try {
            lock.lock();
            if (type == OperationType.PUT) {
                putErrNum++;
            } else if (type == OperationType.GET) {
                getErrNum++;
            }
        } finally {
            lock.unlock();
        }
    }

    public void testRun() {
        int putThreadNumber = scenario.getPutThreadNumber();
        int getThreadNumber = scenario.getGetThreadNumber();
        int getQPS = scenario.getGetQPS();
        int putQPS = scenario.getPutQPS();

        final int getInterval = getThreadNumber == 0 ? 0 : getThreadNumber * (1000 / getQPS);
        final int putInterval = putThreadNumber * (1000 / putQPS);

        final long contentLength = scenario.getContentLength();
        final String bucketName = scenario.getBucketName();
        final byte[] byteArray = chooseByteArray(contentLength);
        final String keyPrefix = "xiao-perf-test-";
        final List<String> uploadedObjects = new ArrayList<String>();
        final List<Long> putLatencyArray = new ArrayList<Long>();
        final List<Long> getLatencyArray = new ArrayList<Long>();
        final PerftestRunner self = this;

        Thread[] putThreads = new Thread[putThreadNumber];
        try {
            for (int i = 0; i < putThreadNumber; i++) {
                Runnable r = new Runnable() {

                    @Override
                    public void run() {
                        while (!self.hasExpired()) {
                            InputStream input = null;
                            try {
                                input = new ByteArrayInputStream(byteArray);
                                String objectKey = buildObjectKey(keyPrefix);

                                log.info("Begin put " + objectKey);
                                Date from = new Date();
                                ObjectMetadata metadata = new ObjectMetadata();
                                metadata.setContentLength(contentLength);
                                ossClient.putObject(bucketName, objectKey, input, metadata);
                                Date to = new Date();
                                long latency = to.getTime() - from.getTime();
                                if (latency < putInterval) {
                                    Thread.sleep(putInterval - latency);
                                } else {
                                    log.warn("Put object " + objectKey + " latency " + latency
                                            + ", exceed max interval " + putInterval);
                                }
                                log.info("Put object " + objectKey + " finished, elapsed " + latency + "ms");

                                try {
                                    lock.lock();
                                    uploadedObjects.add(objectKey);
                                    putLatencyArray.add(latency);
                                } finally {
                                    lock.unlock();
                                }
                            } catch (OSSException oe) {
                                log.warn("Unexpected oss exception occurs when putting object " + oe.getMessage());
                                recordError(OperationType.PUT);
                            } catch (ClientException ce) {
                                log.warn("Unexpected client exception occurs when putting object "
                                        + ce.getMessage());
                                recordError(OperationType.PUT);
                            } catch (Exception e) {
                                log.warn("Other unexpected exception " + e.getMessage());
                                recordError(OperationType.PUT);
                            } finally {
                                if (input != null) {
                                    try {
                                        input.close();
                                        input = null;
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    }
                };

                putThreads[i] = new Thread(r);
            }
        } catch (Exception ex) {
            log.error(ex.getMessage());
            Assert.fail(ex.getMessage());
        }

        Thread[] getThreads = new Thread[getThreadNumber];
        try {
            for (int i = 0; i < getThreadNumber; i++) {
                Runnable r = new Runnable() {

                    @Override
                    public void run() {
                        OSSObject o = null;
                        while (!hasExpired()) {
                            try {
                                String objectKey = null;
                                if (uploadedObjects.size() > 0) {
                                    try {
                                        lock.lock();
                                        if (uploadedObjects.size() > 0) {
                                            int index = new Random().nextInt(uploadedObjects.size());
                                            objectKey = uploadedObjects.get(index);
                                        }
                                    } finally {
                                        lock.unlock();
                                    }
                                } else {
                                    Thread.sleep(50);
                                    continue;
                                }

                                GetObjectRequest request = new GetObjectRequest(bucketName, objectKey);
                                log.info("Begin get " + objectKey);
                                Date from = new Date();
                                o = ossClient.getObject(request);

                                // read data
                                byte[] content = new byte[8196];
                                InputStream in = o.getObjectContent();
                                while (in.read(content) >= 0)
                                    ;

                                Date to = new Date();
                                long latency = to.getTime() - from.getTime();
                                if (latency < getInterval) {
                                    Thread.sleep(getInterval - latency);
                                } else {
                                    log.warn("Get object " + objectKey + " latency " + latency
                                            + ", exceed max interval " + getInterval);
                                }
                                log.info("Get object " + objectKey + " finished, elapsed " + latency + "ms");
                                try {
                                    lock.lock();
                                    getLatencyArray.add(latency);
                                } finally {
                                    lock.unlock();
                                }
                            } catch (OSSException oe) {
                                log.warn("Unexpected oss exception occurs when getting object " + oe.getMessage());
                                recordError(OperationType.GET);
                            } catch (ClientException ce) {
                                log.warn("Unexpected oss exception occurs when getting object " + ce.getMessage());
                                recordError(OperationType.GET);
                            } catch (Exception e) {
                                log.warn("Other unexpected exception " + e.getMessage());
                                recordError(OperationType.GET);
                            } finally {
                                if (o != null) {
                                    try {
                                        o.getObjectContent().close();
                                        o = null;
                                    } catch (IOException e) {
                                        log.error("IO Exception " + e.getMessage());
                                    }
                                }
                            }
                        }
                    }
                };

                getThreads[i] = new Thread(r);
            }
        } catch (Exception ex) {
            log.error(ex.getMessage());
            Assert.fail(ex.getMessage());
        }

        startTime = new Date();
        Thread[] allThreads = joinThreads(putThreads, getThreads);
        waitAll(allThreads);
        endTime = new Date();

        calculateResult(putLatencyArray, OperationType.PUT, putThreadNumber);
        calculateResult(getLatencyArray, OperationType.GET, getThreadNumber);
    }

    public static void deleteFile() {
        File file = new File(fileName);
        if (file.isFile() && file.exists()) {
            file.delete();
        }
    }

    public static void main(String[] args) {
        String realArgs[];
        if (args.length >= 1) {
            realArgs = Arrays.copyOf(args, args.length);
        } else {
            realArgs = new String[] { "get-and-put-1vs1-1KB", "get-and-put-1vs1-100KB", "get-and-put-1vs1-1MB",
                    "get-and-put-1vs1-4MB" };
        }

        deleteFile();
        for (int i = 0; i < realArgs.length; i++) {
            PerftestRunner runner = new PerftestRunner();
            runner.buildScenario(realArgs[i]);
            runner.prepareEnv();
            runner.createBucket();
            runner.writeResult("TestCycle:\n");
            System.out.printf("TestCycle:\n");
            runner.testRun();
        }
    }
}