네이버에서 성능 측정 목적으로 개발된 오픈소스 프로젝트이며, The Grinder라는 오픈소스 기반으로 개발되었다. nGrinder는 서버에 대한 부하 테스트를 하는 것으로 서버의 성능을 측정할 수 있다.
Controller, Agent, Target 서버로 나누어져 있음.
직접 다운받아서 설치해도 되지만 귀찮은 것들을 만져줘야하는 번거로움이 있어 docker사용을 추천한다.
$ docker run -d -v ~/ngrinder-controller:/opt/ngrinder-controller -p 8080:80 -p 16001:16001 -p 12000-12009:12000-12009 ngrinder/controller:3.4
$ docker run -v ~/ngrinder-agent:/opt/ngrinder-agent -d ngrinder/agent:3.4 controller_ip:controller_port
agent는 controller_ip:controller_webport 부분을 옵션 argument로 전달해야 한다.
http://controller_ip:port
id: admin
pw: admin
==> 는 단방향 통신을 뜻함.
-javaagent:/Users/jmlim/.m2/repository/net/sf/grinder/grinder-dcr-agent/3.9.1/grinder-dcr-agent-3.9.1.jar
import HTTPClient.Cookie
import HTTPClient.CookieModule
import HTTPClient.HTTPResponse
import HTTPClient.NVPair
import groovy.json.JsonOutput
import net.grinder.plugin.http.HTTPPluginControl
import net.grinder.plugin.http.HTTPRequest
import net.grinder.script.GTest
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import static net.grinder.script.Grinder.grinder
import static org.hamcrest.Matchers.is
import static org.junit.Assert.assertThat
/**
 * ngrinder get, post 요청 샘플 스크립트.
 */
@RunWith(GrinderRunner)
class SampleGetPostRunner {
    public static GTest test
    public static HTTPRequest request
    public static NVPair[] headers = []
    public static Cookie[] cookies = []
    static String url = "http://테스트할아이피:포트"
    static String commonPath = "/test"
    static String findUrl = url + commonPath + "/find";
    static String createUrl = url + commonPath + "/create"
    @BeforeProcess
    static void beforeProcess() {
        HTTPPluginControl.getConnectionDefaults().timeout = 6000
        test = new GTest(1, "api.test.com")
        request = new HTTPRequest()
        // Set header datas
        List<NVPair> headerList = new ArrayList<NVPair>()
        headerList.add(new NVPair("Content-Type", "application/json"))
        headerList.add(new NVPair("Authorization", "Bearer 토큰토큰"))
        headers = headerList.toArray()
        grinder.logger.info("before process.");
    }
    @BeforeThread
    void beforeThread() {
        test.record(this, "test")
        grinder.statistics.delayReports = true;
        grinder.logger.info("before thread.");
    }
    @Before
    void before() {
        request.setHeaders(headers)
        cookies.each { CookieModule.addCookie(it, HTTPPluginControl.getThreadHTTPClientContext()) }
        grinder.logger.info("before thread. init headers and cookies");
    }
    @Test
    void test() {
        find();
        create();
    }
    /**
     * get test
     */
    void find() {
        HTTPResponse result = request.GET(findUrl)
        resultCheck(result)
    }
    /**
     * post test
     */
    void create() {
        String goodsId = "1234";
        Integer quantity = 2;
        Map<String, Object> paramData = new HashMap<>();
        List<Map<String, Object>> items = new ArrayList<>();
        Map<String, Object> item = new HashMap<>();
        item.put("goods_id", goodsId);
        item.put("quantity", quantity);
        items.add(item);
        paramData.put("items", items);
        String json = JsonOutput.toJson(paramData);
        HTTPResponse result = request.POST(createUrl, json.getBytes())
        resultCheck(result)
    }
    /**
     * 결과값 체크
     * @param result
     */
    void resultCheck(result) {
        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
        } else {
            assertThat(result.statusCode, is(200));
        }
    }
}
import HTTPClient.HTTPResponse
import HTTPClient.NVPair
import net.grinder.plugin.http.HTTPPluginControl
import net.grinder.plugin.http.HTTPRequest
import net.grinder.script.GTest
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import static net.grinder.script.Grinder.grinder
import static org.hamcrest.Matchers.is
import static org.junit.Assert.assertThat
/**
 * ngrinder get, post 요청 샘플 스크립트.
 */
@RunWith(GrinderRunner)
class TestRunner {
    public static GTest test
    public static HTTPRequest request
    public static NVPair[] headers = []
    static String url = "https://타겟url"
    static String commonPath = "/api"
    static String appinitUrl = url + commonPath + "/app"
    @BeforeProcess
    static void beforeProcess() {
        HTTPPluginControl.getConnectionDefaults().timeout = 6000
        test = new GTest(1, "타겟명")
        request = new HTTPRequest()
        grinder.logger.info("before process.");
    }
    @BeforeThread
    void beforeThread() {
        test.record(this, "test")
        grinder.statistics.delayReports = true;
        grinder.logger.info("before thread.");
    }
    @Before
    void before() {
        request.setHeaders(headers)
    }
    @Test
    void test() {
        NVPair param1 = new NVPair("appcode", "999");
        NVPair param2 = new NVPair("appversion", "x");
        NVPair param3 = new NVPair("hashcode", "1122334455667788");
        NVPair[] params = [param1, param2, param3]
        HTTPResponse result = request.POST(appinitUrl, params)
        resultCheck(result)
    }
    /**
     * 결과값 체크
     * @param result
     */
    void resultCheck(result) {
        println result
        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
        } else {
            assertThat(result.statusCode, is(200));
        }
    }
}
import HTTPClient.Codecs
import HTTPClient.HTTPResponse
import HTTPClient.NVPair
import net.grinder.plugin.http.HTTPPluginControl
import net.grinder.plugin.http.HTTPRequest
import net.grinder.script.GTest
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import static net.grinder.script.Grinder.grinder
import static org.hamcrest.Matchers.is
import static org.junit.Assert.assertThat
/**
 * ngrinder get, post 요청 샘플 스크립트.
 */
@RunWith(GrinderRunner)
class MultipartTestRunner {
    public static GTest test
    public static HTTPRequest request
    public static NVPair[] headers = []
    static String url = "http://타겟url"
    static String commonPath = "/api"
    static String moreUploadUrl = url + commonPath + "/upload-app-file"
    @BeforeProcess
    static void beforeProcess() {
        HTTPPluginControl.getConnectionDefaults().timeout = 6000
        test = new GTest(1, "http://타겟명")
        request = new HTTPRequest()
        grinder.logger.info("before process.");
    }
    @BeforeThread
    void beforeThread() {
        test.record(this, "test")
        grinder.statistics.delayReports = true;
        grinder.logger.info("before thread.");
    }
    @Before
    void before() {
        headers = [
                new NVPair("Content-Type", "multipart/form-data")
        ]
        request.setHeaders(headers)
    }
    @Test
    void moreUpload() {
        NVPair param1 = new NVPair("appcode", "999");
        NVPair param2 = new NVPair("appversion", "17");
        NVPair param3 = new NVPair("hashcode", "11223344556677");
        NVPair[] params = [param1, param2, param3]
        NVPair[] files = [new NVPair("userfile", "경로/파일명.확장자")]
        def data = Codecs.mpFormDataEncode(params, files, headers)
        HTTPResponse result = request.POST(moreUploadUrl, data)
        resultCheck(result)
    }
    /**
     * 결과값 체크
     * @param result
     */
    void resultCheck(result) {
        println result
        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
        } else {
            assertThat(result.statusCode, is(200));
        }
    }
}