네이버에서 성능 측정 목적으로 개발된 오픈소스 프로젝트이며, 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));
}
}
}