Skip to content
Advertisement

Gatling JMS scenario does not terminate

I am trying to loadtest a simple request/reply scenario over a rabbitmq broker.

import com.rabbitmq.jms.admin.RMQConnectionFactory;
import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;
import io.gatling.javaapi.jms.JmsProtocolBuilder;

import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import java.util.Collections;
import java.util.UUID;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.jms.JmsDsl.*;

public class ReplySimulation extends Simulation {

    ConnectionFactory connectionFactory = connectionFactory();

    JmsProtocolBuilder jmsProtocolBuilder = jms.connectionFactory( connectionFactory ).usePersistentDeliveryMode().listenerThreadCount( 1 );

    ScenarioBuilder scn = scenario( "Reply Test" ).repeat( 1 ).on(
        exec(
            jms( "request" )
                .requestReply()
                .queue( "requests" ).replyQueue( "loadtest-" + UUID.randomUUID() )
                .textMessage( "Message Body" )
                .jmsType( "textMessage" )
                .check( simpleCheck( message -> true ) ) ) );

    {
        setUp( scn.injectOpen(atOnceUsers( 1 ) )).protocols( jmsProtocolBuilder );
    }

    public ReplySimulation() throws JMSException {

    }

    private ConnectionFactory connectionFactory() throws JMSException {

        RMQConnectionFactory connectionFactory = new RMQConnectionFactory();
        connectionFactory.setUsername( "guest" );
        connectionFactory.setUsername( "guest" );
        connectionFactory.setUris( Collections.singletonList( "amqp://localhost:5672" ) );
        connectionFactory.setVirtualHost( "loadtest" );
        return connectionFactory;
    }
}

This scenario sends a simple text message to the defined queue and then wait on the defined replyQueue for an answer. On the other side of the request queue is simple message listener that reads the JMSReplyTo field and sends a message to that destination.

In the rabbitmq UI I can see that there was a message published to the reply queue and that there was a consumer ack. So that tells me that the round trip was completed.

But for some reason the gatling scenario keeps running giving me output like this:


================================================================================
2021-12-15 16:38:01                                          15s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=0      KO=0     )


---- Reply Test ----------------------------------------------------------------
[--------------------------------------------------------------------------]  0%
          waiting: 0      / active: 1      / done: 0     
================================================================================

So it seems to me that the check did not trigger, otherwise it should read active: 0 / done: 1. I tried having the check return false and null or even throw a RuntimeException or add error output. Nothing did change so it seems indeed that the check did not get executed or all kinds of output get swallowed somehow.

The simulation.log does not contain anything else but two header lines and adding maxDuration to the execution does stop the scenario but it then fails with an ArithmeticException as a division by zero occurs.

The examples I have found so far all look very similar, so I do not see what would be wrong but obviously something just is not working.

EDIT: This is the echo service used in the scenario

public class DeskclientEcho {

    private enum Parameter {
        CONNECTIONS, EXCHANGE, PASSWORD, QUEUE, SSL( 0 ), USER, VHOST;

        private final int argCount;

        Parameter() {

            this( 1 );
        }

        Parameter( int argCount ) {

            this.argCount = argCount;
        }
    }

    private static final Map<Parameter, String> params = new HashMap<>();

    public static void main( String[] args ) throws JMSException, NoSuchAlgorithmException {

        for ( int i = 0; i < args.length; i++ ) {
            Parameter key = switch ( args[i] ) {
                case "-c", "--connections" -> Parameter.CONNECTIONS;
                case "-e", "--exchange" -> Parameter.EXCHANGE;
                case "-p", "--password" -> Parameter.PASSWORD;
                case "-q", "--queue" -> Parameter.QUEUE;
                case "-s", "--ssl" -> Parameter.SSL;
                case "-u", "--user" -> Parameter.USER;
                case "-v", "--vhost" -> Parameter.VHOST;
                default -> throw new IllegalArgumentException("Unknown parameter :" + args[i]);
            };
            i += key.argCount;
            params.put( key, args[i] );
        }

        RMQConnectionFactory factory = new RMQConnectionFactory();
        factory.setUris( Arrays.stream( params.get( Parameter.CONNECTIONS ).split( "," ) ).filter( Objects::nonNull ).toList() );

        if ( params.containsKey( Parameter.SSL ) ) {
            factory.useSslProtocol();
            factory.setUseDefaultSslContext( true );
            factory.setHostnameVerification( true );
        }

        Optional.ofNullable(params.get(Parameter.USER)).ifPresent( factory::setUsername );
        Optional.ofNullable(params.get(Parameter.PASSWORD)).ifPresent( factory::setPassword );
        Optional.ofNullable(params.get(Parameter.VHOST)).ifPresent( factory::setVirtualHost );

        RMQDestination destination = new RMQDestination();
        destination.setAmqp( false );
        destination.setDestinationName( params.get( Parameter.QUEUE ) );
        destination.setAmqpExchangeName( params.get( Parameter.EXCHANGE ) );
        destination.setAmqpQueueName( params.get( Parameter.QUEUE ) );

        Connection connection = factory.createConnection();
        connection.start();
        Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );

        MessageConsumer consumer = session.createConsumer( destination );

        consumer.setMessageListener( message -> {
            System.out.println("Message received");
            try {
                Destination replyTo = message.getJMSReplyTo();
                for (int i = 0 ; i < 10 ; i++) {
                    session.createProducer( replyTo ).send( new RMQTextMessage() );
                }
                System.out.println("Message sent");
            } catch ( JMSException e ) {
                e.printStackTrace();
            }

        } );

    }
}

With this pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>loadtest</groupId>
    <artifactId>deskclient-echo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.rabbitmq.jms</groupId>
            <artifactId>rabbitmq-jms</artifactId>
            <version>2.3.0</version>
        </dependency>
    </dependencies>


</project>

As broker it should suffice to use the default docker image:

docker run -d rabbitmq

Then create the needed vhost or change the simulation to use the default one and start the echo application.

The simulation uses this pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>loadtest</groupId>
    <artifactId>deskclient-requests</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>io.gatling</groupId>
                <artifactId>gatling-maven-plugin</artifactId>
                <version>4.0.1</version>
                <configuration>
                    <simulationClass>ReplySimulation</simulationClass>
                </configuration>
            </plugin>
        </plugins>

    </build>


    <dependencies>
        <dependency>
            <groupId>io.gatling.highcharts</groupId>
            <artifactId>gatling-charts-highcharts</artifactId>
            <version>3.7.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.rabbitmq.jms</groupId>
            <artifactId>rabbitmq-jms</artifactId>
            <version>2.3.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

Does anyone have an idea what I am missing here?

Advertisement

Answer

The core issue is that you don’t define in your echo service any way for Gatling to correlate outbound messages (requests) and inbound messages (responses).

Instead, you’re replying with new RMQTextMessage(), so with completely different JMSMessageID and no JMSCorrelationID.

See https://gatling.io/docs/gatling/reference/current/jms/#other-options.

In your case, you should probably use matchByCorrelationId and propagate it accordingly:

Message response = new RMQTextMessage();
response.setJMSCorrelationID(message.getJMSCorrelationID());
session.createProducer(replyTo).send(response);

Note: Gatling JMS support currently only supports checking (request, response) pairs, so there’s no way to check one-to-many responses like with your for (int i = 0 ; i < 10 ; i++) loop.

Advertisement