Jmeter部署及使用

Apache JMeter™ 应用程序是Apache 组织基于Java开发的压力测试工具, 100% 纯 Java 应用程序。JMeter 最初被设计用于 Web 应用测试,但后来扩展到了其他测试领域,可用于测试静态和动态资源,JMeter 可对服务器、网络或对象模拟巨大的负载,在不同压力类别下测试它们的强度和分析整体性能。得益于Java社区及其架构设计,Jmeter扩展性极强。

  1. 简述

    记录Linux环境下部署Jmeter及相关使用。JMeter 与 Java 8 或更高版本兼容。 出于安全和性能原因,官方强烈建议安装主要版本的最新次要Java版本。官方文档 Apache JMeter - 用户手册

  2. 部署
    • 配置Java环境(必须)

    • 下载页面Apache JMeter - 下载 Apache JMeter

      1
      $ wget https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.3.tgz
    • 解压

      1
      $ tar -zxvf apache-jmeter-5.6.3.tgz
    • 配置环境变量

      1
      2
      3
      4
      5
      6
      7
      8
      $ vim /etc/profile

      # 追加以下内容
      export JMETER_HOME=/data/apache-jmeter-5.6.3
      export CLASSPATH=${JMETER_HOME}/lib/ext/ApacheJMeter_core.jar:${JMETER_HOME}/lib/jorphan.jar:${CLASSPATH}
      export PATH=${JMETER_HOME}/bin:$PATH

      $ source /etc/profile
  3. 相关操作处理
    • 问题:rmi_keystore.jks (No such file or directory)

      Server failed to start: java.rmi.server.ExportException: Listen failed on port: 0; nested exception is:
      java.io.FileNotFoundException: rmi_keystore.jks (No such file or directory)
      An error occurred: Listen failed on port: 0; nested exception is:
      java.io.FileNotFoundException: rmi_keystore.jks (No such file or directory)

      处理方式:

      1
      2
      3
      4
      $ vim /data/apache-jmeter-5.6.3/bin/jmeter.properties

      # 禁用ssl
      server.rmi.ssl.disable=true
    • 格式乱码

      1
      2
      3
      4
      $ vim /data/apache-jmeter-5.6.3/bin/jmeter.properties

      # 修改格式utf-8
      sampleresult.default.encoding=UTF-8
    • 多节点

      • 确保所有从节点安装相同版本的JMeter
      • 防火墙开放端口(默认1099)
      1
      2
      3
      4
      5
      6
      7
      8
      $ vim /data/apache-jmeter-5.6.3/bin/jmeter.properties
      remote_hosts=192.168.1.101:1099,192.168.1.102:1099

      # node节点执行
      $ jmeter-server

      # 控制节点
      $ jmeter -n -t test.jmx -r -l test.jtl -e -o testresult
  4. 命令行参数
    参数 说明 示例
    -n 非GUI模式(必须参数) jmeter -n
    -t 指定测试计划文件(.jmx) jmeter -n -t test.jmx
    -l 指定结果日志文件(.jtl或.csv) jmeter -n -t test.jmx -l result.jtl
    -j 指定JMeter日志文件 jmeter -j jmeter.log
    -p 指定JMeter属性文件 jmeter -n -p config.properties
    -q 指定用户自定义属性文件 jmeter -n -q user.properties
    -L 设置日志级别(DEBUG/INFO等) jmeter -L DEBUG
    -S 从远程服务器加载属性文件 jmeter -S 192.168.1.100:8000/config.properties
    -r 启动远程服务器(分布式模式) jmeter -n -r
    -R 指定远程服务器列表 jmeter -n -R 192.168.1.101,192.168.1.102
    -d 指定JMeter home目录(用于从节点) jmeter -d /opt/jmeter
    -X 强制退出远程服务器 jmeter -n -X
    -G 设置全局属性(覆盖配置文件) jmeter -Gthreads=50
    -J 设置JMeter属性(同GUI中的__P()函数) jmeter -Jramp.up=10
    -D 设置系统属性(Java系统属性) jmeter -Djava.net.preferIPv4Stack=true
    -e 测试后生成HTML报告 jmeter -n -t test.jmx -l result.jtl -e -o report
    -o 指定HTML报告输出目录,指定的文件及文件夹,必须不存在 jmeter -o ./report
  5. 常用组合命令
    • 基础性能测试

      1
      jmeter -n -t test.jmx -l result.jtl -j jmeter.log -e -o report
    • 分布式测试:

      1
      jmeter -n -t test.jmx -R 192.168.1.101,192.168.1.102 -l result.jtl
    • 带参数化测试:

      1
      jmeter -n -t test.jmx -Jusers=100 -Jramp.up=60 -l result.jtl
    • 生成Dashboard报告:

      1
      jmeter -g result.jtl -o report
  6. 常用函数
    • RequestResponse对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      // 获取请求内容
      log.info("Request: " + prev.getSamplerData());

      // 获取响应内容
      log.info("Response: " + prev.getResponseDataAsString());

      // 获取请求名
      log.info("Thread Name: " + prev.getThreadName());

      // 开始采样时间
      log.info("Sample Start: " + (new Date(prev.getStartTime())).toString());

      // 当前 Sampler 的加载时间,单位 ms
      log.info("Load Time: " + prev.getTime().toString());

      // 连接时间,单位 ms
      log.info("Connect Time: " + prev.getConnectTime().toString());

      // 延时
      log.info("Latency: " + prev.getLatency().toString());

      // 响应中的字节数
      log.info("Size in bytes: " + prev.getBytesAsLong().toString());

      // 发送的字节数
      log.info("Sent bytes: " + prev.getSentBytes().toString());

      // 响应 header 的字节数
      log.info("Header size in bytes: " + prev.getHeadersSize().toString());

      // 响应 body 的字节数
      log.info("Body size in bytes: " + prev.getBodySizeAsLong().toString());

      // 合并 Sample 个数
      log.info("Sample Count: " + prev.getSampleCount().toString());

      // 发生错误个数
      log.info("Error Count: " + prev.getErrorCount().toString());

      // 返回数据格式
      log.info('Data type("text"|"bin"|""): ' + prev.getDataType().toString());

      // 响应状态码
      log.info("Response Code: " + prev.getResponseCode().toString());

      // 响应消息
      log.info("Response Message: " + prev.getResponseMessage().toString());

      // 响应的Header
      log.info("Response Header: " + prev.getResponseHeaders().toString());

      // 响应的内容类型
      log.info("ContentType: " + prev.getContentType().toString());

      // 响应的数据编码
      log.info("DataEncoding: " + prev.getDataEncodingNoDefault().toString());
    • 打印Jmeter响应内容(Response)至log,并断言

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      String clue_msg="login_info";			//提示信息分割线
      String Str = "{\"code\":1000"; //期望响应内容(包含即可)

      String response = "";
      response = prev.getResponseDataAsString();


      if(response == ""){
      Failure = true;
      FailureMessage = "系统无响应,获取不到响应数据!";
      log.info(FailureMessage);
      }
      else if(response.contains(Str) == false){
      Failure = true;
      String Msg = "\n ---------------------------------------------"+clue_msg+"---------------------------------------------";
      FailureMessage = Msg + "\n" + "期望结果: " + Str + "\n响应内容: " + response + "\n";
      log.info(FailureMessage);
      }
    • 打印响应时间至log,并断言

      1
      2
      3
      4
      5
      6
      7
      8
      String clue_msg="login_info";
      //期望响应时间 ms
      int expectation_ms = 100;
      int actual_ms = Integer.parseInt(prev.getTime().toString());
      if(actual_ms>expectation_ms){
      time_log = timeMsg + "\n" + "期望响应时间: " + expectation_ms + "ms \n实际响应时间: " + actual_ms + "ms\n";
      log.info(time_log);
      }
    • 操作本地文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      FileWriter fstream = new FileWriter("./testresult/${__time(yyyyMMdd,)}.txt",true);
      //"./testresult/${__time(yyyyMMdd,)}.txt" 写入路径,可以用相对路径,也可以是绝对路径,根据个人实际情况选择,后面的参数 ‘true’,代表追加写入

      BufferedWriter out = new BufferedWriter(fstream);

      out.write(vars.get("group_id")+"\n");
      // group_id 是Jmeter中自己定义的变量,"\n" 代表换行 ","相当于Tab键

      out.close();
      fstream.close();
    • Unicode转UTF-8

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      BeanShell--UTF-8
      //获取响应代码Unicode编码的
      String s2=new String(prev.getResponseData(),"UTF-8");
      //---------------一下步骤为转码过程---------------
      char aChar;
      int len= s2.length();
      StringBuffer outBuffer=new StringBuffer(len);
      for(int x =0; x <len;){
      aChar= s2.charAt(x++);
      if(aChar=='\\'){
      aChar= s2.charAt(x++);
      if(aChar=='u'){
      int value =0;
      for(int i=0;i<4;i++){
      aChar= s2.charAt(x++);
      switch(aChar){
      case'0':
      case'1':
      case'2':
      case'3':
      case'4':
      case'5':
      case'6':
      case'7':
      case'8':
      case'9':
      value=(value <<4)+aChar-'0';
      break;
      case'a':
      case'b':
      case'c':
      case'd':
      case'e':
      case'f':
      value=(value <<4)+10+aChar-'a';
      break;
      case'A':
      case'B':
      case'C':
      case'D':
      case'E':
      case'F':
      value=(value <<4)+10+aChar-'A';
      break;
      default:
      throw new IllegalArgumentException(
      "Malformed \\uxxxx encoding.");}}
      outBuffer.append((char) value);}else{
      if(aChar=='t')
      aChar='\t';
      else if(aChar=='r')
      aChar='\r';
      else if(aChar=='n')
      aChar='\n';
      else if(aChar=='f')
      aChar='\f';
      outBuffer.append(aChar);}}else
      outBuffer.append(aChar);}
      //-----------------以上内容为转码过程---------------------------
      //将转成中文的响应结果在查看结果树中显示
      prev.setResponseData(outBuffer.toString());

    • 变量自增

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      # 传入参数 while_count 并获取值
      while_count=vars.get("while_count");

      # java是强类型 说明下 格式
      int while_count_int= new Long(while_count).intValue();

      # 执行 i++ 自加一操作
      while_count_int++;

      # 将 +1 后的参数传出
      vars.put("while_count",while_count_int+"")

      # while_count++