您现在的位置是:首页 > 文章详情

Jmeter---自定义函数function

日期:2019-03-06点击:526

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.映射事务名与随机数的关系。)
    映射关系:
trans1 trans2 trans3 trans4
0~1005 1006~3014 3015~7064 7065~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即可覆盖到所在事务。

感谢阅读~

原文链接:https://yq.aliyun.com/articles/692799
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章