Use Dynamic Binary Packet for Jmeter
jmeter를 사용한 가변길이 TCP Binary 패킷 처리를 설명합니다.
jmeter를 사용한 가변길이 TCP Binary 패킷 처리를 설명합니다.
Test 시나리오
- 테스트할 서버는 로컬에 1234 port를 listen하고 있습니다.
- 요청 패킷에 요청 seq가 포함됩니다.
- 요청/응답 header 패킷은 동일합니다.
struct header_ {
unsigned char version; // 1 byte
short int seq; // 2 byte
unsigned char str; // 1 byte
short int type; // 2 byte
short int bodyLen; // 2 byte
}
- body는 일반 문자열입니다.
- 요청 패킷 전문은 CSV 파일을 읽어 내용을 만듭니다.
- thread(socket connection) 는 10개로 합니다.
struct header_ {
unsigned char version; // 1 byte
short int seq; // 2 byte
unsigned char str; // 1 byte
short int type; // 2 byte
short int bodyLen; // 2 byte
}
- 요청 패킷 전문은 CSV 파일을 읽어 내용을 만듭니다.
apache jmeter 소스 받기
jmeter의 sampler에서 tcp 가변길이 패킷을 처리할 수 있는 방법이 없으므로, plug-in을 만들어야 합니다. Java Request plug-in을 만들기 위해 jmeter 소스를 받습니다.
jmeter의 sampler에서 tcp 가변길이 패킷을 처리할 수 있는 방법이 없으므로, plug-in을 만들어야 합니다. Java Request plug-in을 만들기 위해 jmeter 소스를 받습니다.
1. github 에서 fork
2. 내 PC에 checkout
git clone https://github.com/lmk/jmeter
git clone https://github.com/lmk/jmeter
3. branch 생성
git branch DynamicLengthTCP
git branch DynamicLengthTCP
4. eclipse import 준비
ant setup-eclipse-project
ant setup-eclipse-project
5. eclipse > File > Import > Existing Projects into Workspace
코드 추가
1. org.apache.jmeter.protocol.java.test 아래 DynamicLengthTCP, SingletonSocketMap class 추가
- DynamicLengthTCP
- AbstractJavaSamplerClient 상속받아 구현했습니다.
- 자세한 내용은 JavaTest class 참고했습니다. (JavaTest를 그대로 복사해서 생성)
- Jmeter에서 생성한 Binary 패킷을 plug-in으로 가져오기 위해서는 hex string으로 변환해서 넘어옵니다. (BinaryTCPClientImpl 참고) "RequestData" 저장된 요청 패킷을 byte[]로 변환합니다.(여기)
- 요청 패킷을 전송합니다.
- 응답 패킷을 header 먼저 받고, body 길이만큼 응답 패킷을 받습니다.
- 받은 Binary 패킷과 상태 정보를, jmeter에서 받을 수 있도록 SampleResult에 담습니다.
- SingletonSocketMap
- Thread별 Socket 재사용을 위한 class 입니다.
- AbstractJavaSamplerClient 상속받아 구현했습니다.
- 자세한 내용은 JavaTest class 참고했습니다. (JavaTest를 그대로 복사해서 생성)
- Jmeter에서 생성한 Binary 패킷을 plug-in으로 가져오기 위해서는 hex string으로 변환해서 넘어옵니다. (BinaryTCPClientImpl 참고) "RequestData" 저장된 요청 패킷을 byte[]로 변환합니다.(여기)
- 요청 패킷을 전송합니다.
- 응답 패킷을 header 먼저 받고, body 길이만큼 응답 패킷을 받습니다.
- 받은 Binary 패킷과 상태 정보를, jmeter에서 받을 수 있도록 SampleResult에 담습니다.
- Thread별 Socket 재사용을 위한 class 입니다.
2. git add 및 commit, push
Export jar
1. Package Explorer 에서 추가한 파일 선택
- DynamicLengthTCP.java
- SingletonSocketMap.java
2. Export > JAR File
3. \lib\ext 경로 아래 저장할 jar 파일명 입력
- \lib\ext에 jar 파일을 넣어두면, jmeter 실행시 plugin으로 인식해서 읽어들입니다.
- github에서 받은 소스의 \lib\ext 경로가 아니라, jmeter 실행파일이 위치한 경로입니다.
- 제 경우는 소스는 "D:\src\jmeter"에 받았고, jmeter 실행파일은 "D:\Tools\apache-jmeter-5.0"에 받았으므로, jar 파일을 저장할 경로는 "D:\Tools\apache-jmeter-5.0\lib\ext" 였습니다.
Test
1. bin/jmeter.bat 실행
2. "Test Plan" 우클릭 > Add > Thread Group추가
- Number of Threads를 10 으로 수정합니다.
- Loop Count는 Forver 체크하면 Stop 할때까지 반복하지만, 테스트니까 기본값 1로 유지합니다.
- Number of Threads를 10 으로 수정합니다.
- Loop Count는 Forver 체크하면 Stop 할때까지 반복하지만, 테스트니까 기본값 1로 유지합니다.
3. "Thread Group" 우클릭 > Add > Config Element > Counter 추가.
- 요청 패킷 seq 증가를 위한 것입니다.
- 시작값, 증가값은 1로 설정합니다.
- seq 는 패킷의 자료형 크기보다 작게 max 값을 정합니다.
- 여기서는 short 형이므로 30000으로 잡았습니다.
- Exported Variable Name에 jmeter 내부에서 사용할 변수명 "reqCount"를 입력합니다.
- 여기서는 short 형이므로 30000으로 잡았습니다.
4. "Thread Group" 우클릭 > Add > Config Element > CSV Data Set Config 추가.
- Filename에 "msg.txt"로 지정합니다.
- msg.txt 파일 내용은 아와 같습니다.
메시지1
메시지2
메시지3
- 여기서는 row전체를 하나의 field를 사용합니다.
- Variable NAmes에 jmeter 내부에서 사용할 변수명 "msg"를 입력합니다.
- Recyle on EOF: row가 끝나면 처음부터 다시 읽도록 True로 설정합니다.
- Stop thread on EOF: row가 끝나도 테스트를 계속 할것이므로 False로 설정합니다.
- msg.txt 파일 내용은 아와 같습니다.
메시지1
메시지2
메시지3
- 여기서는 row전체를 하나의 field를 사용합니다.
5. 테스트 결과를 쉽게 보기 위해 Listener 추가
- "Thread Group" 우클릭 > Add > Listener > View Results Tree
- "Thread Group" 우클릭 > Add > Listener > Summary Report
- "Thread Group" 우클릭 > Add > Listener > Response Time Graph
6. "Thread Group" 우클릭 > Add > Sampler > Java Request
- Classname은 이번에 만든 DynamicLengthTCP 선택합니다.
- HostIP: 접속할 서버 IP 127.0.0.1 입력합니다.
- Port: 접속할 서버 Port 1234 입력합니다.
- Connect Timeout: 접속할때 처리할 timeout 시간은 대략 200 입력합니다.
- Response Timeout: 응답 패킷을 받을때 timeout 시간은 대략 200 입력합니다.
- Re-use connect: 하나의 thread에서 connect socket을 재사용할지 여부인데, True를 입력합니다.
- Set NoDelay, SO_LINGER는 socket 옵션 참고합니다. (모르겠으면 flase, 0)
- Size of response header: 응답 패킷의 해더 사이즈를 입력합니다 (여기서는 8)
- Offset of response body length: 응답 해더에서 body 사이즈가 포함된 위치를 입력합니다 (여기서는 6)
- Size of response body length: body 사이즈가 저장된 크기를 입력합니다 (여기서는 2)
- DynamicLengthTCP 에서는 사이즈가 1, 2, 4 일때만 처리 가능합니다.
- DynamicLengthTCP 에서는 사이즈가 1, 2, 4 일때만 처리 가능합니다.
7. "Java Request" 우클릭 > Add > Pre Processors > BeanShell PreProcessor
- 요청 패킷을 만들기 위해 BeanShell을 작성합니다.
import java.io.*;
import org.apache.jmeter.protocol.tcp.sampler.*;
try {
// msg from CSV Data Set Config
String str = vars.get("msg");
byte[] msg = str.getBytes();
byte[] pkt = new byte[8 + msg.length];
// version
pkt[0] = (byte)0x01;
// seq from Counter
short seq = Short.parseShort(vars.get("reqCount"));
pkt[1] = (byte)(seq>>>8);
pkt[2] = (byte)seq;
// constString
pkt[3] = (byte)0x00;
// type
pkt[4] = (byte)0x00;
pkt[5] = (byte)0x01;
// len
pkt[6] = (byte)(msg.length>>>8);
pkt[7] = (byte)msg.length;
// msg
System.arraycopy(msg, 0, pkt, 8, msg.length);
// byte array to hex string
char[] hexArray = "0123456789ABCDEF".toCharArray();
char[] hexChars = new char[pkt.length *2];
for(int i=0; i<pkt.length; i++) {
int v = pkt[i] & 0xff;
hexChars[i * 2] = hexArray[v>>>4];
hexChars[i * 2 + 1] = hexArray[v & 0x0f];
}
str = new String(hexChars);
vars.put("RequestData", str);
log.info("Request data size: " + str.length());
}catch(Exception e) {
log.info("Detect BeamShell PreProcessor Exception"+ e);
prev.setTopThread(true);
}
import java.io.*;
import org.apache.jmeter.protocol.tcp.sampler.*;
try {
// msg from CSV Data Set Config
String str = vars.get("msg");
byte[] msg = str.getBytes();
byte[] pkt = new byte[8 + msg.length];
// version
pkt[0] = (byte)0x01;
// seq from Counter
short seq = Short.parseShort(vars.get("reqCount"));
pkt[1] = (byte)(seq>>>8);
pkt[2] = (byte)seq;
// constString
pkt[3] = (byte)0x00;
// type
pkt[4] = (byte)0x00;
pkt[5] = (byte)0x01;
// len
pkt[6] = (byte)(msg.length>>>8);
pkt[7] = (byte)msg.length;
// msg
System.arraycopy(msg, 0, pkt, 8, msg.length);
// byte array to hex string
char[] hexArray = "0123456789ABCDEF".toCharArray();
char[] hexChars = new char[pkt.length *2];
for(int i=0; i<pkt.length; i++) {
int v = pkt[i] & 0xff;
hexChars[i * 2] = hexArray[v>>>4];
hexChars[i * 2 + 1] = hexArray[v & 0x0f];
}
str = new String(hexChars);
vars.put("RequestData", str);
log.info("Request data size: " + str.length());
}catch(Exception e) {
log.info("Detect BeamShell PreProcessor Exception"+ e);
prev.setTopThread(true);
}
8. "Java Request" 우클릭 > Add > Pre Processors > BeanShell PreProcessor
- 응답패킷 파싱을 위해 BeanShell을 작성합니다.
import java.nio.ByteBuffer;
import org.apache.jmeter.samplers.SampleResult;
byte[] resData = prev.getResponseData();
int resSize = resData.length;
byte[] buf1 = {resData[4], resData[5]};
byte[] buf2 = {resData[6], resData[7]};
short type = ByteBuffer.wrap(buf1).getShort();
short bodySize = ByteBuffer.wrap(buf2).getShort();
log.info("Rsponse Size: " + resSize + ", type: " + type+ ", bodySize: " + bodySize);
import java.nio.ByteBuffer;
import org.apache.jmeter.samplers.SampleResult;
byte[] resData = prev.getResponseData();
int resSize = resData.length;
byte[] buf1 = {resData[4], resData[5]};
byte[] buf2 = {resData[6], resData[7]};
short type = ByteBuffer.wrap(buf1).getShort();
short bodySize = ByteBuffer.wrap(buf2).getShort();
log.info("Rsponse Size: " + resSize + ", type: " + type+ ", bodySize: " + bodySize);