Jmeter---自定义函数function

Jmeter对比LoadRunner,在场景设置上的不同,包括但不限于:

  1. LoadRunner以脚本被分配的用户数,设置事务占总场景的百分比。
  2. Jmeter以线程数的大小,设置事务占总场景的百分比。
  3. Jmeter很好的支持同一线程组下使用不同协议的请求。
  4. LoadRunner需要以多协议脚本的方法实现不同协议的请求。

对于平常性能测试,这些不同点不会造成太大的问题。
但对于特定需求来说,以上两种工具使用都不太方便,比如:

  1. 若每个线程组下拥有一个事务,因每个线程组至少分配一个线程,所以每个事务的占比>=1/总线程数。有时需要比这更小的占比。
  2. 若给定并发数(线程数),可能出现部分事务无法分配占比的情况。
  3. 当事务数多达30以上时,维护成本较高。

综上两点,我们需要一个测试工具,它可以实现:

  1. 可对场景指定任意并发数
  2. 精准控制事务占比
  3. 快捷维护上述两点。

思路构想:

  • 使用文件维护各事务占比。
  • 以上述文件占比总和为上限,使用均匀分布的概率,生成随机数。
  • 根据生成的随机数,选取对应的事务,发起请求。

Jmeter初期实现:

  • 使用Java Properties文件维护占比
//out.Properties
trans1=10.00
trnas2=20.00
trnas3=40.50
trnas4=29.50
  • 使用Java Request实现均匀分布选取。
  • Jmeter界面长这样
    Annotation_2019_03_07_215318

上述实现初步实现我们的思路,但存在以下缺点:

  1. Jmeter报告统计及原始的jtl结果收集文件里,均包含Java Request事务,不便于获取场景的总TPS。
  2. out.Properties文件仍以百分比维护,一是精确度不高;二是需要做额外的工作,将统计/预测出来的交易数划算为百分比的形式。

Jmeter改进实现:

  • 以Function的形式代替Java Request,规避多余的请求数。
  • 更改out.Properties文件,以整数大小形式说明各事务占比。

更改后界面长这样:
Annotation_2019_03_07_215318

out.Properties可直接维护交易数或占比:

//out.Properties
trans1=1005
trnas2=2009
trnas3=4050
trnas4=2950

自定义的函数实现:

这里有小小的碎碎念,鄱Jmeter源码时,走错了方向,作了很多无用功,却熟悉了Jmeter nonGUI&nonRemote模式下基本的工作机制,心疼下自己0.0

函数功能说明:

  • 参数1:指定场景文件,即Properties文件的路径。
  • 参数2:boolean值,可选,当为true时,每次调用时使用新的随机数种子。
  • 参数3: 函数返回结果赋于新的变量名,可选。
  • 备注:当该函数第一次调用时,函数执行初始化(1.读取Properties;2.映射事务名与随机数的关系。)
    映射关系:
trans1trans2trans3trans4
0~10051006~30143015~70647065~10014

备注:3014 = trans2+trans1 = 1005+1006。7064和10014类推。


Java代码实现Function

package org.apache.jmeter.functions;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.apache.jmeter.engine.util.CompoundVariable;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.util.JMeterUtils;

import java.util.TreeMap;
import java.util.Properties;
import java.io.FileNotFoundException;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Set;
import java.util.Map;
import java.time.Instant;
import java.util.Random;


public class TransactionSwitch extends AbstractFunction{

    private static final List<String> desc = new LinkedList<>();

    private static final String KEY = "__TransactionSwitch"; //$NON-NLS-1$

    static {
        desc.add("PropertiesFileName");
        desc.add("hasRandomSeedVariation(optional)");
        desc.add(JMeterUtils.getResString("function_name_paropt"));
    }

    private Object[] values;

    private Random random = new Random();
    private static int bound ;
    private static TreeMap<Integer,String> map = new TreeMap<>((x,y)->( x==y?0:(x>y?1:0)));
    private static Boolean firstCall = true;

    /**
     * No-arg constructor.
     */
    public TransactionSwitch() {
        firstCall = true;
        map.clear();
    }

    /** {@inheritDoc} */
    @Override
    public String execute(SampleResult previousResult, Sampler currentSampler)
            throws InvalidVariableException {
        if (firstCall){
            init(((CompoundVariable)values[0]).execute().trim());
        }

        if(Boolean.valueOf(((CompoundVariable)values[1]).execute().trim())) {
            random.setSeed(Instant.now().toEpochMilli());
        }

        int nextInt = random.nextInt(bound);
        Map.Entry<Integer,String> entry = map.entrySet().stream().filter(s->
            s.getKey() >= nextInt
        ).limit(1).findFirst().get();

        return entry.getValue();
    }

    private synchronized void init(String configFile){
        Properties prop = new Properties();
        try (var in = new FileInputStream(configFile)){
            prop.load(in);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        bound = 0;
        Set<String> set = prop.stringPropertyNames();
        set.stream().distinct().forEach(s -> {
            int a = Integer.parseInt(prop.getProperty(s).trim());
            bound += a;
            map.put(bound, s.trim());
        });

        bound++;
        firstCall = false;
    }

    /** {@inheritDoc} */
    @Override
    public void setParameters(Collection<CompoundVariable> parameters) throws InvalidVariableException {
        checkMinParameterCount(parameters,2);
        values = parameters.toArray();
    }

    /** {@inheritDoc} */
    @Override
    public String getReferenceKey() {
        return KEY;
    }

    /** {@inheritDoc} */
    @Override
    public List<String> getArgumentDesc() {
        return desc;
    }

}

编译并打包上述代码,放于JMETER_HOME/lib/ext目录下,重启jmeter,可以Jmeter的function helper看到:

_

事后验证

  1. 经测试数据分析与验证,调用该函数不影响负载机性能。
  2. 若某一事务占比较小(比如万分之几),运行2~3min即可覆盖到所在事务。

感谢阅读~

JMeter是一个开源的压力测试工具,用于对Web应用、应用程序接口(API)和其他网络服务进行性能测试。JMeter插件系统允许用户扩展JMeter的功能,并提供了一系列的插件来满足不同的测试需求。 `jmeter-plugins-cmn-jmeter-0.7.jar` 这个文件看起来像是 JMeter 的一个插件包,不过其版本信息似乎有些过时(0.7版),通常推荐使用最新的稳定版本以获得最佳功能和兼容性。 ### 下载步骤: 1. **访问官方站点**:首先前往 [Apache JMeter官方网站](https://jmeter.apache.org/) 或者 [Apache JMeter GitHub页面](https://github.com/apache/jmeter),获取关于最新版本的信息以及如何安装插件的相关指导。 2. **查找所需插件**:在网站上搜索 `Common Plugins` 或者直接输入 `jmeter-plugins-cmn` 来查找相关的插件列表。请注意,此插件的名称可能会有所不同,在最新的文档或API页面中,应找到对应的功能描述及下载链接。 3. **确认版本和兼容性**:在选择下载之前,请务必确认该插件是否与您当前使用的 JMeter 版本兼容。如果插件有多个版本供选择,请查阅它们之间的差异说明,选择最适合您的版本。 4. **下载并安装**:点击对应的下载链接,将文件保存到本地计算机上。然后打开JMeter,通过“添加JAR或目录”按钮,在JMeter的配置文件 `lib/ext` 目录下导入插件的 .jar 文件。确保在启动JMeter前已更新配置文件路径以包含新导入的插件。 5. **验证安装**:完成以上步骤后,重启JMeter并检查插件是否成功加载。通常,新插件会出现在JMeter的“取样器”、“监听器”等组件菜单中,你可以从这里确认插件是否可用。 ### 安全提示: - 确保下载来自可信源的插件。避免从不可信或未经授权的第三方站点下载文件,以防恶意软件感染。 - 使用杀毒软件扫描文件以确保安全性。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值