JMeter provides functions that can be used in the sampler. While writing complex test-plan you feel that JMeter is lacking some of the methods. You use Beanshell script to define your own custom method. JMeter invokes Beanshell interpreter to run script the script. This works fine as long as you don’t generate high load (high number of threads). But once JMeter tries to generate high load it run out of resources and slows down dramatically. If JMeter custom functions are used instead then JMeter is able to generate high load effortlessly. The only problem is figuring out implementation requirement and how to integrate with JMeter.
There are hardly any document provided by JMeter about custom function implementation. But after looking through JMeter source code and Googling, I found the way to implement JMeter custom function.

Custom method implementation

Lets dive into the details of implementation. There are certain requirement that should be satisfied. These are as following.

  • Function class package name must contain “.functions.”
  • Function class must extend AbstractFunction and implement execute(), setParameters(), getReferenceKey() and getArgumentDesc() methods
  • Make jar file and put in <JMETER_HOME>/lib/ext directory and restart JMeter

Package name

JMeter is design in such a way that it can run without GUI(Grapical User Interface). It loads the core classes and execute the test-plan. It provides high priority to core classes and prefer to load those classes first.
In order to make sure that GUI and core/backend doesn’t get mixed it segregate the classes based on the package name. It tries to follow convention that the function implmentation class should be present in package which should contain ‘functions’ word in it e.g com.code4reference.jmeter.functions. Under the hood it looks in jmeter.properties file and try to find the following property values.

classfinder.functions.contain=.functions.

As you can see the default value provided is ".functions.". you can change this to something else, but you have to make sure that the same word should exist in the custom function class package name. It’s preferred that to keep default value. Once you define the package now it’s time to write the Function implementation class.

Function implementation class

While writing this class you have to implement the following methods.

  1. String getReferenceKey(): Name of the function which can be called from sampler. Convention is to put two “__”(underscore) before the name of the function e.g __TimeInMillis and function name should be same as the class name which has implemented this function. This function name should be stored in some static final String variable so that it can’t be change during the execution.
  2. List getArgumentDesc(): This method basically returns the argument description in a list of string. This description appears in Function helper (shown in the below picture)
  3. void setParameters(Collection parameters): This method is called by JMeter and it passes the values passed in the function call. The variables are passed as collection of CompoundVariable. This method is get called even there is no argument provided. In this method global variable can be set and accessed in execute() method.
  4. String execute(SampleResult previousResult, Sampler currentSampler): JMeter passes previous SampleResult and the current SampleResult. This method returns a string which get used as a replacement value for the function call. This method get called by multiple threads so it has to be threadsafe. Strange thing about this method is that after processing the arguments the result has to be converted to string and returned
JMeter function helper

JMeter function helper

Source code

In sample source code below, I have implemented one function called __TimeInMillis. This method returns time in milliseconds after adjusting the current time with provided offset. For example, this ${__TimeInMillis(2000)} method call returns 1371413879000 when current time is 1371413877000.

package com.code4reference.jmeter.functions;

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

import org.apache.jmeter.engine.util.CompoundVariable;
import org.apache.jmeter.functions.AbstractFunction;
import org.apache.jmeter.functions.InvalidVariableException;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

public class TimeInMillis extends AbstractFunction {

    private static final List<String> desc = new LinkedList<String>();
    private static final String KEY = "__TimeInMillis";
    private static final int MAX_PARAM_COUNT = 1;
    private static final int MIN_PARAM_COUNT = 0;
    private static final Logger log = LoggingManager.getLoggerForClass();
    private Object[] values;

    static {
        desc.add("(Optional)Pass the milliseconds that should be added/subtracted from current time.");
    }
   

    /**
     * No-arg constructor.
     */
    public TimeInMillis() {
        super();
    }

    /** {@inheritDoc} */
    @Override
    public synchronized String execute(SampleResult previousResult, Sampler currentSampler)
            throws InvalidVariableException {
        //JMeterVariables vars = getVariables();
        Calendar cal = Calendar.getInstance();

        if (values.length == 1 ) { //If user has provided offset value then adjust the time.
            log.info("Got one paramenter");
            try {
                Integer offsetTime =  new Integer(((CompoundVariable) values[0]).execute().trim());
                cal.add(Calendar.MILLISECOND, offsetTime);
            } catch (Exception e) { //In case user pass invalid parameter.
                throw new InvalidVariableException(e);
            }           
        }

        return String.valueOf(cal.getTimeInMillis());
    }

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

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

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

I have highlight some of the crucial part of the code. In line 19, function name is set where as in line 26 function description is provided. In line 60, the number of arguments are check and made sure that the right number of arguments have been provided. The main part of code is highlighted between 44 to 51 where current time is adjusted and returned as string object.
If you are interested to check other function implementation then checkout the entire source code present on github/Code4Reference.
Once code is written, compile it and make jar file and place it in <JMETER_HOME>/lib/ext directory. You can get a sample Gradle script for building jar file in this post. If you don’t know about Gradle, then you can use commands to generate jar file. The easiest way of creating jar file by exporting the package in Eclipse and select the export destination as Jar file.

Hope this blog helped you in some way. If you like this blog then please share it. You can also leave your comment below. You can find Facebook page here.

, ,
Trackback

no comment untill now

Add your comment now