Build executable JAR for Gatling load test
Asked Answered
N

7

18

I am new to Gatling (2.1.2) and want to do a small prototype project to show to my colleagues.

According to the quick start page, there are several ways I can run a simulation with Gatling:

  1. decompress the Gatling bundle into a folder and drop my simulation files into user-files/simulations folder. bin/gatling.sh will compile and run the simulation files.
  2. use the gatling-maven-plugin maven plugin to execute the simulation.
  3. create a project with gatling-highcharts-maven-archetype, and run the Engine class.

and I found those problems

For 1, it is hard to add dependencies for simulation classes. I have to figure out what the jars are needed and drop them to the lib folder.

For 2, it requires maven to be installed.

For 3, it only runs from an IDE

I just want a simple executable JAR file with all the dependencies bundled together (my simulation, Gatling and third party), and run it from any machine (like EC2 instances).

Is there a way to achieve this?

Update 1:

I tried method 3, but moving all the project files from test folder to main, and used maven-assembly-plugin to build a jar with dependencies. When I tried to run the file, I got the following error:

Exception in thread "main" java.lang.ExceptionInInitializerError
    at Engine$.delayedEndpoint$Engine$1(Engine.scala:7)
    at Engine$delayedInit$body.apply(Engine.scala:4)
    at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.collection.immutable.List.foreach(List.scala:381)
    at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
    at scala.App$class.main(App.scala:76)
    at Engine$.main(Engine.scala:4)
    at Engine.main(Engine.scala)
Caused by: java.nio.file.FileSystemNotFoundException
    at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171)
    at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157)
    at java.nio.file.Paths.get(Paths.java:143)
    at io.gatling.core.util.PathHelper$.uri2path(PathHelper.scala:32)
    at IDEPathHelper$.<init>(IDEPathHelper.scala:7)
    at IDEPathHelper$.<clinit>(IDEPathHelper.scala)
    ... 11 more

I guess this is something to do with Gatling configuration, but don't know what has gone wrong.

Nazareth answered 11/1, 2015 at 23:43 Comment(4)
If you want to build a PoC, why don't you stick first with the deploy strategies that are officially supported?Chocolate
@StephaneLandelle, I've actually tried the official strategy, and that's how I found I had to drop all the jars into the lib folder. I just want to know if building a runnable JAR is possible or not, and why.Nazareth
@Philippe, you might be able to use method 1 in my question. What you need to do is to unzip the Gatling bundle zip file, and paste your scenario scala file into the ${GATLING}/user-files/simulations folder. Any dependent jar should go into ${GATLING}/lib (you need to create the folder if it's not in the bundle). Then you can run Gatling by ${GATLING}/bin/gatlin.sh. The file tells you everything how it work.Nazareth
This is currently the best practice until Gatling provides a standalone runnable file, which is what I wanted.Nazareth
A
12

I tried to do something similar. I could not use Maven as well. I will try to remember how I did this.

1) I have configured maven-assembly-plugin to generate single JAR with dependencies like this:

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
    </configuration>
</plugin>

You need to ensure all required libraries (gatling, scala runtime, zinc compiler) are present on your resulting classpath.

2) Check the scope of your dependencies as Maven packs only classes defined with scope=compile by default. The most simple way is probably to use no test dependencies.

3) Create a launch script, e.g. launch.sh. It should contain something like this:

#!/bin/sh
USER_ARGS="-Dsomething=$1"
COMPILATION_CLASSPATH=`find -L ./target -maxdepth 1 -name "*.jar" -type f -exec printf :{} ';'`
JAVA_OPTS="-server -XX:+UseThreadPriorities -XX:ThreadPriorityPolicy=42 -Xms512M -Xmx2048M -XX:+HeapDumpOnOutOfMemoryError -XX:+AggressiveOpts -XX:+OptimizeStringConcat -XX:+UseFastAccessorMethods -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false ${JAVA_OPTS}"
java $JAVA_OPTS $USER_ARGS -cp $COMPILATION_CLASSPATH io.gatling.app.Gatling -s your.simulation.FullClassName

To explain, I took gatling`s own launch script for inspiration. Note mainly the presence of target directory in classpath parameter definition.

4) Compile your compiled target directory and launch.sh to a single directory and distribute this (e.g. as archive). Then you can the scenarios by executing ./launch.sh.

I know this is not a standard solution, but it worked for me. Hopefully it will help you too. If you have any problems or tips to improve, please share with us.

Attaboy answered 7/4, 2015 at 10:29 Comment(2)
Sorry about the late response. That's almost the same as what I ended up doing. Hope the Gatling team will give official support to this.Nazareth
How can I specify list of simulation classes? -sf doesn't work as it requires folder path not the package path in bundled jarWestward
B
7

I think is a bit late for that but I face kinda the same problem related here, but instead to use maven I used gradle. Guess that the approach it's the same, a bit mix of the first solution and something or my own.

First, define a gradle build file with gatling dependencies and a task to build a fatjar

apply plugin: 'scala'
version 0.1

dependencies {
  compile group: 'io.gatling', name: 'gatling-test-framework', version: '2.1.7'
  compile group: 'com.typesafe.akka', name: 'akka-actor_2.11', version: '2.4.7'
  compile group: 'org.scala-lang', name: 'scala-library', version: '2.11.7'
}

repositories{
   mavenCentral()
   mavenLocal()
}


task fatJar(type: Jar) {
   manifest {
       attributes 'Implementation-Title': 'Preparing test',  
          'Implementation-Version': version,
          'Main-Class': 'io.gatling.app.Gatling'
   }
   baseName = project.name + '-all'
      from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } {
        exclude 'META-INF/MANIFEST.MF'
        exclude 'META-INF/*.SF'
        exclude 'META-INF/*.DSA'
        exclude 'META-INF/*.RSA'

   }
   with jar
}

That task executed as

gradle clean build fatJar

will generate a self contained jar which will run the Gatling main class as default. So tell it witch test you want to run is made with the standard '-s' parameter.

So last step is create, if you want, a script to run it. I will "steal" the script for the first comment and change a bit

#!/bin/sh

if [ -z "$1" ];
then
    echo "Test config tool"
    echo
    echo "Running Parameters : "
    echo
    echo " <Config file> : Test definition file. Required"
    echo
   exit 0;
 fi

USER_ARGS="-DCONFIG_FILE=$1"
JAVA_OPTS="-server -XX:+UseThreadPriorities -XX:ThreadPriorityPolicy=42 -Xms512M -Xmx2048M -XX:+HeapDumpOnOutOfMemoryError -XX:+AggressiveOpts -XX:+OptimizeStringConcat -XX:+UseFastAccessorMethods -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false ${JAVA_OPTS}"
java $JAVA_OPTS $USER_ARGS -jar test-project-all-0.1.jar -s FunctionalTestSimulation -nr

In my case I will run the same test with different, easy to configure, parameters so my Simulation is always the same. All my scala files are compiled by gradle and package in the jar that's mean they are in the classpath, changing the "FunctionalTestSimulation" name for a Script variable make easy adapt this script for something more generic.

Guess that make a Maven version will be easy.

Hope that help somebody.

Update with folder structure After a request will add an small draft of the folder structure for the project:

test-project
    |_ build.gradle
    |_ src
        |_ main
            |_ scala
            |_ resources
    |_ runSimulation.sh
    |_ configFile.conf

When have time will provide a link to my github with a working one. Cheers

Bindery answered 7/7, 2016 at 10:54 Comment(6)
This was really helpful, thanks. One small thing, I see that you have imported scala-library twice at the top, in different formats. You could remove thatCalicle
Thanks, didn't realize that!Bindery
can you please also share your project directory structure that works for this Gradle script? e.g. where are your tests & resources locatedElimination
Aye, I'll prepare some example code and will let it in my github. But will take a time, since those days I'm really busy. That's ok? If you are in rush just drop me an email and I'll try to explain it.Bindery
To make the script build reports remove -nr and add "compile "io.gatling.highcharts:gatling-charts-highcharts:2.3.0"" into gradle dependenciesErotogenic
this gives "com.typesafe.config.ConfigException$Missing: No configuration setting found for key 'akka.stream'". supposedly, because multiple "reference.conf" (with akka config) need to be merged to one while building the fat jar file.Elimination
G
5

You can always create a simple Java class that starts Gatling with the Gatling.fromArgs. With this setup you can have all in just one happy executable jar. Let this class be the jar mainClass instead of "io.gatling.app.Gatling". This example is for a scala simulation class "my.package.MySimulation".

import scala.Option;
import io.gatling.app.Gatling;
import io.gatling.core.scenario.Simulation;

public class StartSimulation {

  public static void main(String[] args) {
    Gatling.fromArgs(new String[]{}, new Option<Class<Simulation>>() {

        private static final long serialVersionUID = 1L;

        @Override
        public int productArity() {
            return 0;
        }

        @Override
        public Object productElement(int arg0) {
            return null;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Class<Simulation> get() {
            try {
                return (Class<Simulation>) Class.forName("my.package.MySimulation");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        public boolean isEmpty() {
            return false;
        }

        @Override
        public boolean canEqual(Object o) {
            return false;
        }
    });
  }
}
Gibbosity answered 12/8, 2015 at 15:51 Comment(3)
Thanks for your answer. The problem is more to do with dependencies. What I ended up doing is write a custom main class as you did, and use maven to package the project into an executable JAR with dependencies.Nazareth
Unfortunately, the fromArgs method was marked private to io.gatling in version 2.2.3, so to use this trick you now need to add something like: package io.gatling import io.gatling.app.{Gatling, SelectedSimulationClass} object GatlingHack { def fromArgs(args: Array[String], selectedSimulationClass: SelectedSimulationClass): Int = Gatling.fromArgs(args, selectedSimulationClass) }Fitzhugh
Note, though, that Gatling is not meant to be embedded in this way, as pointed out by Stephane Landelle a few days before making the fromArgs method private: groups.google.com/d/msg/gatling/387pDAweZEY/Va6TsErHAAAJFitzhugh
A
2

I had a similar issue, I fixed it as following:

Inside Gatling package there is bin/ and take a look at gatling.sh. You see that it simply adds certain configurations into classpath and then runs io.gatling.app.Gatling class in gatling-compiler-<version_number>.jar. So, all you need to do is to make a jar that includes compiler, add configurations and tests to classpath and run io.gatling.app.Gatling. steps:

add compiler dependency:

<dependency>
        <groupId>io.gatling</groupId>
        <artifactId>gatling-compiler</artifactId>
        <version>${gatling.version}</version>
    </dependency

create jar with dependencies:

  <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.4.1</version>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <finalName>${project.build.finalName}</finalName>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

create test jar (this includes your gatling tests)

 <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.4</version>
            <executions>
                <execution>
                    <goals>
                        <goal>test-jar</goal>
                    </goals>
                    <configuration>
                        <excludes>
                            <exclude>src/test/resources/*</exclude>
                        </excludes>
                        <finalName>${project.build.finalName}</finalName>
                    </configuration>
                </execution>
            </executions>
        </plugin>

create a package out of your configuration. You can use maven assembly for that. What I usually do, is to create a separate module that handles creating the package for different environments. This package contains your gatling.conf, logback.xmland all the other resources you application wants including test data. Now you basically have three packages: application.jar, application-tests.jar and application-conf.zip. Unzip application-conf.zip, copy application.jarand application-tests.jarin the same folder.

In this folder, You need to create target/test-classes/ folder, just leave it empty. In my case, it was required. I think you can some how change that in gatling.conf. But I am not sure how.

Run

java -cp ".:application-test.jar:application.jar" io.gatling.app.Gatling  
Athabaska answered 9/11, 2015 at 9:50 Comment(0)
M
0

For me this worked


task fatJar(type: Jar, dependsOn: ['gatlingClasses', 'processResources']) {
  group =  "build"
  manifest {
    attributes 'Implementation-Title': project.name,
      'Implementation-Version': project.version,
      'Main-Class': 'myPackage.MyMainClass'
  }

  exclude 'META-INF/MANIFEST.MF'
  exclude 'META-INF/*.SF'
  exclude 'META-INF/*.DSA'
  exclude 'META-INF/*.RSA'

  duplicatesStrategy = DuplicatesStrategy.EXCLUDE

  archiveClassifier = "all"

  from files(sourceSets.main.output.classesDirs)
  from files(sourceSets.gatling.output)
  from {
    configurations.gatlingRuntimeClasspath
      .filter { it.exists() }
      .collect { it.isDirectory() ? it : zipTree(it) }
  }


  with jar
}

Modified version of https://medium.com/@suman.maity112/run-gatling-scenarios-as-executable-jar-bfe32c3d9af5

Maintenon answered 28/7, 2023 at 16:11 Comment(0)
H
-1

I use IntelliJ Idea and I got this fixed by right clicking on the scala folder > Mark Directory as > Test Sources Root . Now Execute "Engine" and you will be all good !

Heida answered 7/7, 2017 at 2:42 Comment(1)
This also works if the directory is marked as sources root, not test. Convenient way to quickly run a test I suppose.Adila
R
-1

I've recently blogged about this Creating a versionable, self-contained (fat-/uber-) JAR for Gatling tests, the source of which can be found in jamietanna/fat-gatling-jar.

For a Maven project, the steps would be as follows.

The main things you need are to add the dependency on gatling-charts-highcharts:

<project>
    <!-- ... -->
    <dependencies>
        <dependency>
            <groupId>io.gatling.highcharts</groupId>
            <artifactId>gatling-charts-highcharts</artifactId>
            <version>${gatling.version}</version>
        </dependency>
    </dependencies>
</project>

Next, you need to make sure your Gatling scenarios/simulations are in src/main instead of src/test.

Finally, you can use the maven-shade-plugin to build an executable JAR which uses Gatling's CLI runner as the mainClass:

<project>
    <!-- ... -->
    <build>
        <plugins>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-shade-plugin</artifactId>
              <version>3.1.1</version>
              <configuration>
                <filters>
                  <!-- https://stackoverflow.com/a/6743609 -->
                  <filter>
                    <artifact>*:*</artifact>
                    <excludes>
                      <exclude>META-INF/*.DSA</exclude>
                      <exclude>META-INF/*.SF</exclude>
                      <exclude>META-INF/*.RSA</exclude>
                    </excludes>
                  </filter>
                </filters>
              </configuration>
              <executions>
                <execution>
                  <phase>package</phase>
                  <goals>
                    <goal>shade</goal>
                  </goals>
                  <configuration>
                    <transformers>
                      <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>io.gatling.app.Gatling</mainClass>
                      </transformer>
                    </transformers>
                  </configuration>
                </execution>
              </executions>
            </plugin>
        </plugins>
    </build>
</project>  
Recha answered 21/11, 2018 at 21:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.