标签:scope 最新 color 最大 密码 方法参数 ssi 请求头 beanshell
目录:
比较点
|
JMeter
|
Ngrinder
|
LoadRunner
|
---|---|---|---|
实现语言 | Java | java/python | java/VB/C/.NET |
使用方式 | C/S或Command | B/S | C/S |
支持分布式 | master/slave | controller/agent | master/slave |
开源方式 | 免费,完全开源 | 免费,完全开源 | 收费 |
支持协议 | 多种协议 | 多种协议 | 多种协议 |
资源监控 | monitor/plugin,如果二开,需要查找plugin的源码 | monitor方式 | 自带资源监控功能 |
社区活跃度 | 文档完善 | 有中文社区 | 网上资料和相关培训很多,购买正版还可以得到技术支持 |
是否需要编码 | 基本不需要 | 需要,Jython/Groovy | 需要 |
脚本的维护 | 本地 | 内置SVN,可以修改成git | 本地 |
脚本录制 | 可使用BadBoy进行录制 | 可通过PTS插件进行录制 | 自带录制功能 |
可扩展性 | 可增加plugin,输出结果可以再加工,扩展性强 | 可增加plugin,扩展性强 | 通过扩展函数库实现 |
安装 | 简单,解压即可 | 简单,可以下载安装包或绿色包解压 | 安装包比较大,安装繁琐 |
平台化 | 有开源或云端的压测平台 | 本身具备 | 无 |
nGrinder是韩国一家公司居于Grinder二次开发的一个性能平台。nGrinder具有 开源、易用、高可用、高扩展等特性,在Grinder基础上实现了多测试并行,通过web管理,实现了集群,同时支持Groovy和Jython脚本语言,(官方上说Groovy的性能会更好),也实现了对目标服务的监控以及插件的扩展,实现更多用户虚拟用户并发(官方上说,8G内存的4核cpu机器可以支持高到8000个虚拟用户),在同一线程中,不断重复的执行测试脚本,来模拟很多并发用户
Controller
1. 分发调度测试任务
2.协调测试进程
3. 整理和显示测试的统计结果
4. 用户创建和修改脚本
Monitor
1.用于监控被测服务器的系统性能(例如:CPU/MEMORY)
2.必须部署在被测服务器上
Agent
1.压测任务的拉取
2.在代理服务器上加载运行测试进程和线程
3. 监控施压机器的系统性能(例如:CPU/MEMORY/网卡/磁盘)
nGrinder 是一款在一系列机器上执行 Groovy 或 Jython 测试脚本的应用,内部引擎是基于 Grinder。 nGrinder 使用 controller 和 agent 分别包装了 Grinder 的 console 和 agent ,而且扩展了多种功能使其能够支持并发测试。
归纳
以linux 这里采用的版本是centos 6 64bit,性能测试工具不建议在Windows上部署。
选择最新的war包。
服务器端启动: # java -XX:MaxPermSize=200m -jar ngrinder-controller-3.5.2.war --port 8080
这样nGrinder的管理页面就部署好,你可以简单的把ngrinder-controller的功能理解为性能测试展示和控制,后面会进行详细介绍。
打开网址:
默认用户名和密码都为admin
下载agent tar包(进入主页,点击 admin →Download Agent),然后进行解压 tar -xvf ngrinder-agent-3.5.2.tar
解压agent 后,目录结构如下,编辑进入__agent.conf
如果controller和agent在同一台机器,则修改 agent.controller_host=127.0.0.1
启动agent: [ops@qa.test.jmeter-00.hz ngrinder-agent]$ ./run_agent.sh
Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy 可以使用其他 Java 语言编写的库。
Groovy是一种基于Java平台的面向对象语言。 Groovy 1.0于2007年1月2日发布,其中Groovy 2.4是当前的主要版本。 Groovy通过Apache License v 2.0发布。
目前最新版本为2.5.3。
Groovy中有以下特点:
动态类型
类型对于变量,属性,方法,闭包的参数以及方法的返回类型都是可有可无的,都是在给变量赋值的时候才决定它的类型, 不同的类型会在后面用到,任何类型都可以被使用,即使是基本类型 (通过自动包装(autoboxing)). 当需要时,很多类型之间的转换都会自动发生,比如在这些类型之间的转换: 字符串(String),基本类型(如int) 和类型的包装类 (如Integer)之间,可以把不同的基本类型添加到同一数组(collections)中。
运算符重载
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
Groovy类和java类一样,完全可以用标准java bean的语法定义一个Groovy类。但作为另一种语言,可以使用更Groovy的方式定义类,这样的好处是,可以少写一半以上的javabean代码。
(1)不需public修饰符
如前面所言,Groovy的默认访问修饰符就是public,如果Groovy类成员需要public修饰,则根本不用写它。
(2)不需要类型说明
同样前面也说过,Groovy也不关心变量和方法参数的具体类型。
(3)不需要getter/setter方法
在很多ide(如eclipse)早就可以为程序员自动产生getter/setter方法了,在Groovy中,不需要getter/setter方法--所有类成员(如果是默认的public)根本不用通过getter/setter方法引用它们(当然,如果一定要通过getter/setter方法访问成员属性,Groovy也提供了它们)。
(4)不需要构造函数
不再需要程序员声明任何构造函数,因为实际上只需要两个构造函数(1个不带参数的默认构造函数,1个只带一个map参数的构造函数--由于是map类型,通过这个参数可以构造对象时任意初始化它的成员变量)。
(5)不需要;结尾
Groovy中每一行代码不需要分号作为结束符。
数字:RandomStringUtils.randomNumeric(length);
字母:RandomStringUtils.randomAlphabetic(length);
字母加数字:RandomStringUtils.randomAlphanumeric(length);
所有ASCCII字符:RandomStringUtils.randomAscii(length);
自定义混合字符:RandomStringUtils.randomAscii(length, string);
数字:int random_number = ThreadLocalRandom.current().nextInt(min_num, max_num);
common项目:"/resources/account.txt"
maven项目:Thread.currentThread().getContextClassLoader().getResource("/account.txt").getPath();
maven项目获取文件内容:ReflectionUtils.getCallingClass(0).getResourceAsStream("/account.txt").getText("UTF-8")
txt每行单数据: String[] file_arrary = new File("/resources/account.txt") as String[];
String file_data = file_arrary[arrary_index];
txt每行双数据: String[] file_arrary = new File("/resources/account.txt") as String[];
String data_one = file_arrary[arrary_index].split(",")[0];
String data_two = file_arrary[arrary_index].split(",")[1];
另一种方法:
List<String> reqDataArrList = new File(dataFilePath).readLines()
String data_one = reqDataArrList.get(arrary_index).split(",")[0];
String data_two = reqDataArrList.get(arrary_index).split(",")[1];
txt每行多数据可参考双数据方法。也可以参考json方式存储:
BufferedReader txt_content=new BufferedReader(new FileReader(new File("/resources/account.txt")))
data_json = new JSONObject()
String text_line = ""
while(( text_line=txt_content.readLine())!=null){
data_json.put(text_line.split(",")[0],text_line.split(",")[1])
}
String data_one = data_json.keys[0]
String data_two = data_json.getString(data_one)
覆盖写入: def write = new File(file_path, file_name).newPrintWriter();
write.write(write_text);
write.flush();
write.close()
追加写入: def write = new File(file_path, file_name).newPrintWriter();
write.append(write_text);
write.flush();
write.close()
json文件读取: String json_str = new File(file_path).getText("UTF-8")
def json_object = RecorderUtils.parseRequestToJson(json_str)
长度:json_object.length()
关键字:json_object.keys()
添加元素:json_object.put(name, value)
修改元素:json_object.put(name, value)
删除元素:json_object.remove(name, value)
获取对应value:json_object.getString(name)
字符串截取:String new_str = old_str[0..3]
字符串替换:String string = str.replace("old","new")
字符串统计:int count = string.count("char")
字符串转化:int int_num = Integer.parseInt(string)
1)设置多个静态Gtest对象:
public static GTest test1
public static GTest test2
2)实例化多个Gtest对象:
test1 = new GTest(1, "test1");
test2 = new GTest(2, "test2");
3)监听多个test请求:
test1.record(this, "test1")
test2.record(this, "test2")
4)定义多个test方法:
public void test1(){
grinder.logger.info("---ones: {}---", grinder.threadNumber+1)
}
public void test2(){
grinder.logger.info("---twos: {}---", grinder.threadNumber+1)
}
add方法: List<NVPair> paramList = new ArrayList<NVPair>();
paramList.add(new NVPair("name", "value"));
paramList.add(new NVPair("name", "value"));
params = paramList.toArray();
new方法: params = [new NVPair("name", "value"), new NVPair("name", "value")];
日志级别(三种常见): grinder.logger.info("----before process.----");
grinder.logger.warn("----before process.----");
grinder.logger.error("----before process.----");
日志限定(仅打印error级别) :
1)导入依赖包
import ch.qos.logback.classic.Level;
import org.slf4j.LoggerFactory;
2)设定级别
@BeforeThread
LoggerFactory.getLogger("worker").setLevel(Level.ERROR);
3)设置打印语句
@test
grinder.logger.error("----error.----");
日志输出(输出所有进程日志):将每个agent的.ngrinder_agent/agent.conf中一项修改为agent.all_logs=true
日志打印:打印变量:grinder.logger.error("{},{}",variable1,variable2); // 换行或缩进可在""中加\n或\t
1) 登录产生cookie
@BeforeThread
login_get_cookie(); // 调用登录方法
cookies = CookieModule.listAllCookies(HTTPPluginControl.getThreadHTTPClientContext()); // 配置cookie管理器
2) 读取控制器中cookie
@Before
cookies.each { CookieModule.addCookie(it, HTTPPluginControl.getThreadHTTPClientContext()) }
1)通过url加参数直接访问:
post方法: HTTPResponse result = request.POST("http://192.168.2.135:8080/blogs", params, headers)
get方法: HTTPResponse result = request.GET("http://192.168.2.135:8080/blogs", params, headers)
参数是json:设置请求头参数{"Content-Type": "application/json"}
2)通过参数化所有请求数据为json对象(导入import org.ngrinder.recorder.RecorderUtils)
HTTPResponse result = RecorderUtils.sendBy(request, req_data_json)
HTTPResponse result = RecorderUtils.sendBy(request, req_data_json)
1)引用依赖包:
import net.grinder.scriptengine.groovy.junit.annotation.RunRate
2)设置运行次数百分比(所有test设定的比例值不够100,那不满的部分不运行,比如设定总比80,只运行这80部分):
@RunRate(50) // 数字代表百分比
@Test
public void test1(){}
@RunRate(50) // 数字代表百分比
@Test
public void test2(){}
int tota_agents = Integer.parseInt(grinder.getProperties().get("grinder.agents").toString()) // 设置的总加压机数
int total_processes = Integer.parseInt(grinder.properties().get("grinder.processes").toString()) // 设置的总进程数
int total_threads = Integer.parseInt(grinder.properties().get("grinder.threads").toString()) // 设置的总线程数
int total_runs = Integer.parseInt(grinder.properties().get("grinder.runs").toString()) // 设置的总运行次数(若设置的是运行时长,则得到0)
int agent_number = grinder.agentNumber // 当前运行的加压机编号
int process_number = grinder.processNumber // 当前运行的进程编号
int thread_number = grinder.threadNumber // 当前运行的线程编号
int run_number = grinder.runNumber // 当前运行的运行次数编号
// 传递接口参数runNumber(即def runNumber = grinder.runNumber)
private int getIncrementId(int runNumber){
// 获取压力机总数、进程总数、线程总数
int totalAgents = Integer.parseInt(grinder.getProperties().get("grinder.agents").toString())
int totalProcess = Integer.parseInt(grinder.getProperties().get("grinder.processes").toString())
int totalThreads = Integer.parseInt(grinder.getProperties().get("grinder.threads").toString())
// 获取当前压力机数、进程数、线程数
int agentNum = grinder.agentNumber
int processNum = grinder.processNumber
int threadNum = grinder.threadNumber
// 获取唯一递增数id
int incrementId = agentNum * totalProcess * totalThreads + processNum * totalThreads + threadNum + totalAgents * totalProcess * totalThreads * runNumber
return incrementId
}
1)需要设置静态变量:private enum WhenOutOfValues { AbortVuser, ContinueInCycleManner, ContinueWithLastValue }
2)传递接口参数fileDataList(即def fileDataList = new File(dataFilePath).readLines())
private int getLineNum(def fileDataList) {
// 获取当前运行数、数据读取行数、数据最大行数
int counter = getIncrementId(grinder.runNumber)
int lineNum = counter + 1
int maxLineNum = fileDataList.size() - 1
// 读取最大值的判断处理
WhenOutOfValues outHandler = WhenOutOfValues.AbortVuser
if (lineNum > maxLineNum) {
if(outHandler.equals(WhenOutOfValues.AbortVuser)) {
lineNum = maxLineNum //grinder.stopThisWorkerThread()
} else if (outHandler.equals(WhenOutOfValues.ContinueInCycleManner)) {
lineNum = (lineNum - 1) % maxLineNum + 1
} else if (outHandler.equals(WhenOutOfValues.ContinueWithLastValue)) {
lineNum = maxLineNum
}
}
return lineNum
}
public static String getTestInfo(){
String time_string = ""
// 获取压测时设置的进程总数、线程总数、运行次数并在log中打印
int all_process = grinder.getProperties().getInt("grinder.processes", 1) // 设置的总进程数
int all_threads = grinder.getProperties().getInt("grinder.threads", 1) // 设置的总线程数
int all_runs = grinder.getProperties().getInt("grinder.runs", 1) // 设置的总运行次数(若设置的是运行时长,则得到0)
int all_duration = grinder.getProperties().getLong("grinder.duration", 1) // 设置的总运行时长(若设置的是运行次数,则得到0)
// 格式化时间毫秒输出(输出格式00:00:00)
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss")
formatter.setTimeZone(TimeZone.getTimeZone("GMT+00:00"))
String all_duration_str = formatter.format(all_duration)
if (all_duration_str.equals("00:00:00"))
time_string = "Test information: the processes is "+all_process+", the threads is "+all_threads+", the run count is "+all_runs+"."
else
time_string = "Test information: the processes is "+all_process+", the threads is "+all_threads+", the run time is "+all_duration_str+"."
return time_string
}
String property = grinder.getProperties();
grinder.logger.info("------- {}", property) ;
HTTPResponse result = request.POST("http://192.168.2.135:8080/blogs", params, headers)
返回的文本:grinder.logger.info("----{}----", result.getText()) // 或者result.text
返回的状态码:grinder.logger.info("----{}----", result.getStatusCode()) // 或者result.statusCode
返回的url:grinder.logger.info("----{}----", result.getEffectiveURI())
返回的请求头所有参数:grinder.logger.info("---\n{}---", result)
返回的请求头某参数:grinder.logger.info("----{}---- ", result.getHeader("Content-type"))
匹配状态码:assertThat(result.getStatusCode(), is(200))
匹配包含文本:assertThat(result.getText(), containsString("success"))
public int getVusers() {
int totalAgents = Integer.parseInt(grinder.getProperties().get("grinder.agents").toString());
int totalProcesses = Integer.parseInt(grinder.getProperties().get("grinder.processes").toString());
int totalThreads = Integer.parseInt(grinder.getProperties().get("grinder.threads").toString());
int vusers = totalAgents * totalProcesses * totalThreads;
return vusers;
}
if (result.statusCode == 301 || result.statusCode == 302) {
grinder.logger.error("Possible error: {} expected: <200> but was: <{}>.",result.getEffectiveURI(),result.statusCode);
} else {
assertEquals((String)result.getEffectiveURI(), result.statusCode, 200)
assertThat((String)result.getEffectiveURI(), 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.AfterThread import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread import org.junit.Test import org.junit.runner.RunWith import static org.junit.Assert.assertThat import static org.hamcrest.Matchers.is import static net.grinder.script.Grinder.grinder @RunWith (GrinderRunner) // 每个测试类加这注解 class TestRunner{ public static GTest test1 public static HTTPRequest request @BeforeProcess // 在每个进程启动前执行 static void beforeProcess() { HTTPPluginControl.getConnectionDefaults().timeout = 8000 test1 = new GTest( 3 , "queryLoanCounts" ); request = new HTTPRequest() } @BeforeThread // 在每个线程执行前执行 void beforeThread() { test1.record( this , "queryLoanCounts" ); // 延时生成报告 grinder.statistics.delayReports= true ; } private NVPair[] headers() { return [ new NVPair( "Content-type" , "application/json;charset=UTF-8" ) ]; } @Test public void queryLoanCounts(){ String json = "{\"uid\": \"1_2000008\"}" ; HTTPResponse result = request.POST( "http://core.com/query-loaning-count" ,json.getBytes(), headers()); grinder.logger.info(result.getText()); if (!result.statusCode == 200 ) { grinder.logger.warn( "Warning. The response may not be correct. The response code was {}." , result.statusCode); } else { assertThat( "判断响应结果:" ,result.statusCode, is( 200 )); } } @AfterThread public void afterThread(){ } } |
使用工具进行脚本的编写
进程和线程的用法
6.1、创建脚本
6.2、 创建测试计划
6.3、执行控制面板
6.4、测试报告面板
思考:
为什么要部署多个agent?
当线程数量过多的时候,实际的压力可能不会提升。由于agent本身的瓶颈,导致压力下发不下去。 当压力测试结果表现为:线程数量增多,响应时间和tps数却无变化,说明agent本身已经达到瓶颈了,无法再增加更多的压力。 这时候就需要部署多个agent给被测服务。
标签:scope 最新 color 最大 密码 方法参数 ssi 请求头 beanshell
原文地址:https://www.cnblogs.com/unknows/p/14348965.html