![Using custom JAR files in JMeter Using custom JAR files in JMeter](../img/blog/custom-jar-files/custom-jar.jpg)
Using custom JAR files in JMeter
JMeter has many plug-ins and downloadable JAR Files you can use to support your testing, it is also possible to write your own custom Jar files and use these as well.
Why would you want to do that, you may say, well whilst it is unlikely that you would need to create anything too complicated.
You may feel the need to create utilities to support your own in-house performance testing or just something to make your testing process easier.
We could easily write a custom Jar file to push the results of test run to any reporting tools your organisation uses, if they expose their API's, at the end of the test execution cycle.
Alternatively, you could write something that sends your results to a mail server or web server for the purposes of results distribution.
For the purposes of this post lets imagine a situation where your company has a dashboard technology that displays the status of development builds or testing progress through a series of APIs, there are many examples of these type of status boards and they are common in many technology departments.
And let’s write a JAR file to output our JMeter test results in the form of JSON which can be sent to this imaginary endpoint we will then use the Custom Class we create in a JMeter test script.
Let’s get started!
Integrated Development Environment¶
You are going to need an Integrated Development Environment (IDE) to build the Jar file, the good news is there are many community editions available to download for free.
For the purposes of this post we will use the community edition of IntelliJ, which can be downloaded here.
Building the JAR File¶
Our JAR file is going to be simple and you will probably use something more complex and more dynamic to account for a variable number of scripts and timing points.
As writing complex JAR files is not the objective of this post our simple example will be enough to demonstrate how custom JAR files can be implemented as well as giving you ideas on their possible uses for your company.
Our simple JAR file code is available here to download and can also be found at the end of this post.
Let’s have a look at the important bits of the JAR file.
We have a function that takes some input values and formats as JSON, as we have stated earlier your real-world example is likely to be much more complex.
The buildResultsResponse
function takes some test details and timing point data and simply transforms it.
At the end we call another function sendResultsToService
public static String buildResultsResponse(String testName,
String executionDateTime,
String testComments,
String timingPointOneName,
String timingPointOneResponseDuration,
String timingPointTwoName,
String timingPointTwoResponseDuration,
String timingPointThreeName,
String timingPointThreeResponseDuration,
String timingPointFourName,
String timingPointFourResponseDuration) {
// Instance of JSON object
JSONObject opJsonObj = new JSONObject();
// Add values to JSON
opJsonObj.put("testName", testName);
opJsonObj.put("executionDate", executionDateTime);
opJsonObj.put("comments", testComments);
opJsonObj.put("timingPointOverview", new String[] { timingPointOneName, timingPointTwoName, timingPointThreeName, timingPointFourName });
JSONObject responseTimesJsonObj = new JSONObject();
responseTimesJsonObj.put("timingPointNameOne", timingPointOneName);
responseTimesJsonObj.put("timingPointOneDuration", timingPointOneResponseDuration);
responseTimesJsonObj.put("timingPointNameTwo", timingPointTwoName);
responseTimesJsonObj.put("timingPointTwoDuration", timingPointTwoResponseDuration);
responseTimesJsonObj.put("timingPointNameThree", timingPointThreeName);
responseTimesJsonObj.put("timingPointThreeDuration", timingPointThreeResponseDuration);
responseTimesJsonObj.put("timingPointNameFour", timingPointFourName);
responseTimesJsonObj.put("timingPointFourDuration", timingPointFourResponseDuration);
opJsonObj.put("timingPointDetail", responseTimesJsonObj);
// Send JSON to sendResultsToService as an argument
sendResultsToService(opJsonObj.toString(4));
// Return a string so we can see the output in JMeter
return opJsonObj.toString();
}
The buildResultsResponse
function simply outputs the JSON we have created, this is where you could send the output to an endpoint or service.
public static void sendResultsToService(String jsonString){
// This is printed output but could be a API call to a monitoring endpoint
System.out.println(jsonString);
}
As you can see it is a very simple example, as the aim of this post is to show you how we can use these functions from within JMeter.
If we add the correct number of programme arguments to IntelliJ and run it, we can see our output in the run window.
That was a very brief overview of our dummy JAR file code, if you want to write something more complex there are many good resources available online.
Before we move on to the JMeter part we need to generate a JAR file from out code which in IntelliJ is as simple as selecting Build -> Build Artifacts
from the menu options.
This will build a JAR file and output to a location on your filesystem defined in File -> Project Structure -> Artifacts
.
Building the JMeter Test¶
Before we can show you how to use the JAR file in your JMeter script we need to create a test that will output some values that we can pass into our JAR file.
As with our JAR file our test will be equally as simple, we will create a Thread Group with four dummy samplers and a teardown Thread Group to call the JAR file and output the results.
The thread groups and the samplers have a JSR223 Post Processor that extracts the information we want from the test.
We have a Thread Group with a Post Processor which captures the testName, testComments and executionDateTime and writes these to a set of properties.
//get Thread Group Name
props.put("testName", prev.getThreadName());
//get Test Script Name
props.put("testComments", args[0]);
//get StartTime
def start_time = prev.getStartTime()
props.put("executionDateTime", (new Date(start_time).toString()));
Under each sampler there is also a Post Processor that captures the sampler name and duration.
//get Sampler Name
props.put("timingPointOneName", prev.getSampleLabel());
//get Response Time
props.put("timingPointOneResponseDuration", prev.getTime().toString());
Above is from dummy-sampler-1 and these are repeated across all samplers with the property names changing to reflect the sampler they are running in.
When the test executes we create 11 properties which we will use in our teardown Thread Group when we call the functions in our custom JAR file, these properties are:
- testName,
- testComments,
- executionDateTime,
- timingPointOneName,
- timingPointOneResponseDuration,
- timingPointTwoName,
- timingPointTwoResponseDuration,
- timingPointThreeName,
- timingPointThreeResponseDuration,
- timingPointFourName,
- timingPointFourResponseDuration.
Using the JAR File¶
This is what we have been building up to through this whole post, the point at which we can use our JAR file in our JMeter test and it is extremely straightforward.
Our teardown Thread Group has a single JSR223 Sampler which calls the function created in the JAR file.
Let’s look at this in detail
Firstly we import our Custom Class using
import com.octoperf.custom.OctoPerfCustom
If you recall the package and Class name from our Custom JAR file was
package com.octoperf.custom;
public class OctoPerfCustom {
We then surround our custom function with a try catch statement, so we can catch and output any errors
try {
...
catch (Throwable ex) {
log.error("ERROR: ", ex);
throw ex;
}
We then create a new instance of our Class
OctoPerfCustom octoCustom = new OctoPerfCustom();
If you remember our buildResultsResponse
function returns a String, which is there for test purposes.
If we call the function we can write the results to a variable, which is what we are doing here, we call it passing in all the properties we have saved during the test execution.
String responseString = octoCustom.buildResultsResponse(props.get("testName"),
props.get("executionDateTime"),
props.get("testComments"),
props.get("timingPointOneName"),
props.get("timingPointOneResponseDuration"),
props.get("timingPointTwoName"),
props.get("timingPointTwoResponseDuration"),
props.get("timingPointThreeName"),
props.get("timingPointThreeResponseDuration"),
props.get("timingPointFourName"),
props.get("timingPointFourResponseDuration"));
We can then output the response to the console
log.info(responseString);
If we now run our test, we can see the same JSON we saw when the JAR file was run locally.
This indicates that JMeter is calling our functions and therefore if we had a valid endpoint for the request rather than just outputting the message then our results would have been sent.
And that’s about it, not more complicated than that.
Conclusion¶
Clearly it's an extremely simple example, and you would need to allow for multiple iterations and multiple tests running in parallel if you wanted to use this approach in your results gathering.
The objective of the post was to show you how it can be accomplished and give you enough information to go away and create something suitable for your organisations should you wish to do so.
The JMeter test and JAR files are available to download here:
JAR File code in full.
package com.octoperf.custom;
import org.json.JSONObject;
public class OctoPerfCustom {
public static void main(String[] args) {
String testName = args[0];
String executionDateTime = args[1];
String testComments = args[2];
String timingPointOneName = args[3];
String timingPointOneResponseDuration = args[4];
String timingPointTwoName = args[5];
String timingPointTwoResponseDuration = args[6];
String timingPointThreeName = args[7];
String timingPointThreeResponseDuration = args[8];
String timingPointFourName = args[9];
String timingPointFourResponseDuration = args[10];
buildResultsResponse(testName, executionDateTime, testComments, timingPointOneName,
timingPointOneResponseDuration, timingPointTwoName, timingPointTwoResponseDuration,
timingPointThreeName, timingPointThreeResponseDuration, timingPointFourName,timingPointFourResponseDuration);
}
public static void sendResultsToService(String jsonString){
// This is printed output but could be a API call to a monitoring endpoint
System.out.println(jsonString);
}
public static String buildResultsResponse(String testName, String executionDateTime, String testComments, String timingPointOneName, String timingPointOneResponseDuration,
String timingPointTwoName, String timingPointTwoResponseDuration, String timingPointThreeName, String timingPointThreeResponseDuration,
String timingPointFourName, String timingPointFourResponseDuration) {
// Instance of JSON object
JSONObject opJsonObj = new JSONObject();
// Add values to JSON
opJsonObj.put("testName", testName);
opJsonObj.put("executionDate", executionDateTime);
opJsonObj.put("comments", testComments);
opJsonObj.put("timingPointOverview", new String[] { timingPointOneName, timingPointTwoName, timingPointThreeName, timingPointFourName });
JSONObject responseTimesJsonObj = new JSONObject();
responseTimesJsonObj.put("timingPointNameOne", timingPointOneName);
responseTimesJsonObj.put("timingPointOneDuration", timingPointOneResponseDuration);
responseTimesJsonObj.put("timingPointNameTwo", timingPointTwoName);
responseTimesJsonObj.put("timingPointTwoDuration", timingPointTwoResponseDuration);
responseTimesJsonObj.put("timingPointNameThree", timingPointThreeName);
responseTimesJsonObj.put("timingPointThreeDuration", timingPointThreeResponseDuration);
responseTimesJsonObj.put("timingPointNameFour", timingPointFourName);
responseTimesJsonObj.put("timingPointFourDuration", timingPointFourResponseDuration);
opJsonObj.put("timingPointDetail", responseTimesJsonObj);
// Send JSON to sendResultsToService as an argument
sendResultsToService(opJsonObj.toString(4));
// Return a string so we can see the output in JMeter
return opJsonObj.toString();
}
}