Sunday, February 7, 2016

Gatling simple simulation example

This a simple Gatling simulation example. The POST request is already explained in my earlier blog Gatling post request with JSON body
The example will run the scenario using 5 users for 10 minutes duration with no pauses.
import net.liftweb.json.DefaultFormats
import net.liftweb.json.Serialization._
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.core.scenario.Simulation

class MySimulation extends Simulation {
  case class Person(name: String)

  val createPerson = http("Create person")
    .post("/person")
    .body(StringBody(session => write(Person("Jack"))(DefaultFormats))).asJSON

  val httpProtocol = http
    .baseURL("https://localhost:8080/application")
    .disableFollowRedirect
    .disableAutoReferer
    .disableCaching
    .connectionHeader("keep-alive")

  val myScenario = scenario("My scenario").during(10) {
    exec(createPerson)
  }.inject(rampUsers(5).over(1))

  setUp(myScenario)
    .pauses(disabledPauses)
    .protocols(httpProtocol)
    .assertions(global.failedRequests.count.is(0))
}

Gatling post request with JSON body

To make a POST request in Gatling with JSON request body use the following code -
import net.liftweb.json.DefaultFormats
import net.liftweb.json.Serialization._

object PersonScript {
  case class Person(name: String)

  val createPerson = http("Create person")
    .post("/person")
    .body(StringBody(session => write(Person("Jack"))(DefaultFormats))).asJSON
}

The Person case class represents the JSON body structure you want to post.
The StringBody code line converts the case object into JSON representation for post.
Ensure to have following dependency in your classpath.
 <dependency>
  <groupId>net.liftweb</groupId>
  <artifactId>lift-json_2.11</artifactId>
  <version>3.0-M7</version>
 </dependency>

Gatling read json response and store as list in session

Consider a REST end point /persons which returns JSON response of array of Person object. The Person object having property named id. You want to store all the returned id in personIds list in session to use it further. The REST response is like this -
[
  {
    "id": 1,
    "name": "Jack",
  },
  {
    "id": 2,
    "name": "Jill"
  }
]
  val getAllPersons= http("Get all persons")
    .get("/persons")
    .check(status.is(200), jsonPath("$..id").findAll.optional.saveAs("personIds"))

The above code will extract id values and store as list in personIds.
If REST response returns empty list then the above code will fail with error -
jsonPath($..id).findAll.exists, found nothing

Using optional in the chain helps avert the error. Only if the findAll returns any thing the saveAs will execute and will work without failure.

Spring reloadable message source

Spring provides you to externalize your messages so that it can be changed without application restart. Add following snippet to your spring configuration.
    <bean id="messageSource"
          class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>${messages.file}</value>
            </list>
        </property>
        <property name="cacheSeconds" value="1" />
    </bean>

The messages.file is pointing to property loaded by Spring PropertyConfigurer. For development purposes you can keep the messages file bundled with your project in classpath but for real deployment it will be outside.
The value should be like classpath:messages where the messages.properties file is kept in src/main/resources. The value can be messages.file=file:D:/config/messages if its kept out at this location.
The Spring messages are by default internalized so if you have done that setup it can pickup files like messages_en_GB.properties as per the locale.

Spring externalize application configuration

To configure your application is different environment using different setup use the following in Spring framework -
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:default.properties</value>
                <value>file:${APPLICATION_CONFIG_HOME}/application.properties</value>
            </list>
        </property>
        <property name="ignoreResourceNotFound" value="true" />
        <property name="searchSystemEnvironment" value="true" />
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
    </bean>
By default it will read the default.properties available in your application classpath. Typically it is available at src/main/resources/default.proerties.

Setup environment variable APPLICATION_CONFIG_HOME which points to directory where customer application.properties is available. If the file is available then it will override those properties which you have specified in that file.

For local development user need not required to define this as mostly developers will use the default.properties.

This setup provies an option to override if required in a particular environment e.g. QA, performance, staging, production, etc.

Spring data JPA Pessimistic lock

If you want to lock a database record to execute certain business logic and do not want any other thread to update the same then do the following -
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

import javax.persistence.LockModeType;

public interface PersonRepository extends CrudRepository {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select p from Person p where p.id = :id")
    Person findOneAndLock(@Param("id") int id);
}
In this example Person is an entity and I want to lock a Person record based on id. The service layer method must be in transaction to use this method.

Simple Gatling setup with Maven

Just add the following maven pom snippet to your project pom.xml.

Create maven style project structure of src/test/scala. As per the example below your default simulation class is com.company.project.MySimulation. If you want to create different name/package pass -Dsimulation= to execute that simulation.

All of the properties mentioned in the pom can be overridden via command line using -D option.

Execute mvn test to run your gatling performance simulation.

<project>
    <properties>
        <simulation>com.company.project.MySimulation</simulation>
        <applicationUrl>http://localhost:8080/application</applicationUrl>
        <noOfUsers>1</noOfUsers>
        <durationInMinutes>1</durationInMinutes>
        <rampUpInMinutes>1</rampUpInMinutes>
    </properties>
    <dependencies>
        <dependency>
            <groupId>io.gatling.highcharts</groupId>
            <artifactId>gatling-charts-highcharts</artifactId>
            <version>2.2.0-M3</version>
        </dependency>
        <dependency>
            <groupId>io.gatling</groupId>
            <artifactId>gatling-app</artifactId>
            <version>2.2.0-M3</version>
        </dependency>
        <dependency>
            <groupId>net.liftweb</groupId>
            <artifactId>lift-json_2.11</artifactId>
            <version>3.0-M7</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.2</version>
            </plugin>
            <plugin>
                <groupId>io.gatling</groupId>
                <artifactId>gatling-maven-plugin</artifactId>
                <version>2.2.0-M2</version>
                <executions>
                    <execution>
                        <phase>test</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                        <configuration>
                            <configFolder>src/test/resources</configFolder>
                            <simulationsFolder>src/test/scala</simulationsFolder>
                            <simulationClass>${simulation}</simulationClass>
                            <noReports>false</noReports>
                            <jvmArgs>
                                <jvmArg>-DapplicationUrl=${applicationUrl}</jvmArg>
                                <jvmArg>-DnoOfUsers=${noOfUsers}</jvmArg>
                                <jvmArg>-DdurationInMinutes=${durationInMinutes}</jvmArg>
                                <jvmArg>-DrampUpInMinutes=${rampUpInMinutes}</jvmArg>
                                <jvmArg>-Xms2048M</jvmArg>
                                <jvmArg>-Xmx2048M</jvmArg>
                            </jvmArgs>
                            <propagateSystemProperties>true</propagateSystemProperties>
                            <failOnError>true</failOnError>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>