虽然之前有进行过一段时间的性能测试,但没有做过websocket请求的性能测试,此次项目用到了websocket请求,在摸索中也踩了一些坑,记录下来,供后续参考。
1、需求内容
notebook建模首先需要创建容器,容器创建好后,就可以发送建模请求,建模请求方式是websocket。本次就需要测试notebook建模的性能指标,比如能够承受最多多少个并发等。
2、实现流程
2.1 生成jobid
1)理想的情况是每个请求(不同用户)可以在不同的容器发送建模请求,就需要调用创建容器时每次入参的jobid不同,所以在前一个job提交接口,就需要生成不同的jobid;生成不同的jobid,需要该接口入参的jobName和jobCode不同,所以生成jobid步骤对入参进行了参数化处理&随机数生成处理。入下图:
2)请求体内容如下:
{"jobName":"perf_${__Random(1,3000,id)}","projectId":1727,"jobType":7,"runResource":"default","sparkCcolony":"default","fileDir":"default","jobCode":"t0_1544168471${__Random(1,300,id)}"}
3)jobid要作为下一个接口的入参,所以还需要使用正则表达式提取器。
- 引用名称:下个请求将要引用到的变量名。
- 正则表达式:提取你想要内容的正则表达式,小括号()表示提取,也就是说对于你想要提取的内容用它括起来。
- 模板:使用提取到的第几个值。因为可能有多个值匹配,所以要使用模板。从1开始匹配,依次类推。这里只有一个,所以填写 1 1 1即可。
- 匹配数字:表示如何取值。0表示随机取值,1表示全部取值。这里只有一个,填1即可。
- 缺省值:表示参数没有取到值的话,默认给它的值。一般不填。
这里其中jobID就是下一个请求要引入的变量。
2.2 调用创建容器接口
生成jobid后,就可以去创建容器了。请求如下:
后续我们要使用到该接口返回参数notebookurl,但该url有ip,port以及path,下一个接口只需要使用ip和port,所以要使用正则提取出该字段,然后对url进行ip,port提取。
正则提取:
添加后置处理器:
代码块:
String ip= vars.get("notebookUrl");
log.info("首次进入beanshell。");
if (ip != null && !"null".equals(ip)){
log.info("ip的初始值为:" + ip);
ip = ip.substring(7);
log.info("ip的值为:" + ip);
ip = ip.substring(ip.indexOf(":")+1);
log.info("ip的值为:"+ip);
ip = ip.substring(ip.indexOf("/")+1,ip.lastIndexOf("/"));
log.info("ip的值为:"+ip);
ip = ip.substring(0,ip.lastIndexOf("/"));
log.info("ip的最终值为:"+ip);
vars.put("notebookUrl",ip);
}else{
log.info("容器未创建,需要重新调用创建容器接口。");
vars.put("notebookUrl", "null");
}
为何要加null判断?因为有时候容器已经事先创建成功了,就不需要进行截取了。
2.3 确定容器创建成功
创建容器接口是一个异步请求,所以我们要循环调用info查询接口,判断容器是否创建成功。使用while控制器事先,只有ip为空时会循环查询,条件如下:
同样需要对相应的结果进行抽取,获取notebookurl;以及后置处理,获取截取后的ip和port,但是此时的后置处理逻辑和上个有所不同。
正则提取:
ip截取后置处理:
String ip= vars.get("notebookUrl");
log.info("轮询/zero/job/info接口,查询该接口是否有notebookUrl返回,直到返回notebookUrl不为空后退出。");
if (ip != null && !"null".equals(ip)){
log.info("轮询后ip的初始值为:" + ip);
ip = ip.substring(7);
log.info("轮询后ip的值为:" + ip);
ip = ip.substring(ip.indexOf(":")+1);
log.info("轮询后ip的值为:"+ip);
ip = ip.substring(ip.indexOf("/")+1,ip.lastIndexOf("/"));
log.info("轮询后ip的值为:"+ip);
ip = ip.substring(0,ip.lastIndexOf("/"));
log.info("轮询后ip的最终值为:"+ip);
vars.put("notebookUrl",ip);
}
2.4 创建kernel接口
该接口会生成websocket请求的kernelId和sessionID,请求很简单,稍微复杂的是需要对结果进行提取,均使用后置处理器提取。
sessionId,随机生成32位16进制数:
import java.util.Random;
public class randomHexString {
public static String randomImpl(int len) {
try {
StringBuffer result = new StringBuffer();
for (int i = 0; i < len; i++) {
result.append(Integer.toHexString(new Random().nextInt(16)));
}
String sid = result.toString().toUpperCase();
log.info("生成的32位16进制数sessionid是:" + sid);
return sid;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
}
}
int len = 32;
String sid = randomHexString.randomImpl(len);
vars.put("sid",sid);
kernelId从返回response中提取:
import org.json.*;
String response_data = prev.getResponseDataAsString();
JSONObject data_obj = new JSONObject(response_data);
String diaryList_str = data_obj.get("kernel").get("id").toString();
log.info("获取到的kernelID为:" + diaryList_str);
vars.put("kernelID",diaryList_str);
2.5 发送建模请求
每个建模请求还有一个需要随机生成的数字,msgID,需要在发送请求前用前置处理器生成;同时通过wiershark以及chrome自带浏览器的功能,检测到建模会发送3个websocket请求。
1)建模请求1前置处理:
import java.util.Random;
public class randomHexString {
public static String randomImpl(int len) {
try {
StringBuffer result = new StringBuffer();
for (int i = 0; i < len; i++) {
result.append(Integer.toHexString(new Random().nextInt(16)));
}
String sid = result.toString().toUpperCase();
log.info("生成的32位16进制数msgId1是:" + sid);
return sid;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
}
}
int len = 32;
String msgId1 = randomHexString.randomImpl(len);
vars.put("msgId1",msgId1);
2)建模请求1请求参数:
{"header":{"msg_id":"${msgId1}","username":"username","session":"${sid}","msg_type":"kernel_info_request","version":"5.0"},"metadata":{},"content":{},"buffers":[],"parent_header":{},"channel":"shell"}
其他两个请求同上,不一一赘述;后续再添加查看结果树,以及汇总报告等监听,即完成本次需求。
3、知识总结
本次性能脚本测试,了解了之前没有使用过debug sample,前置脚本,后置脚本书写逻辑,正则表达式的抽取、变量参数化以及while控制器脚本书写逻辑(while控制器的空必须是"null")。这些细节内容将后续单列总结。