DEV Community

cuongld2
cuongld2

Posted on

Loadtest REST API with gatling opensource

Hi folks, today I'm gonna show you how to loadtest your rest api with gatling tool.

I.Why loadtest:

Load testing gives confidence in the system & its reliability and performance. Load Testing helps identify the bottlenecks in the system under heavy user stress scenarios before they happen in a production environment.

Alt Text

You can find out more about loadtest in this guru99

II.What is gatling:

Gatling is a loadtest tool created based on Scala, Akka and Netty.

You can findout more information about gatling in official gatling site

Gatling provides the DSL (Domain specific language) for us to create the scenario and setup for loadtesting.

Below is the code snippet for a gatling simulation scenario.

class ConcurrentRequests extends BaseSimulation{

  val scn: ScenarioBuilder = scenario("Check concurrentRequests for get song detail api")
    .exec(
      http("Get song detail api")
        .get(GeneralConfigs.COCCOCMUSIC_MOBILE_API_SONGS+"/"
          +EnvironmentConfigs.COCCOCMUSIC_MOBILE_API_TESTDATA_SONGDETAIL.zingmp3
        +"_"+"audio")
        .check(status.is(200)))

  setUp(scn.inject(nothingFor(4),atOnceUsers(1),rampUsersPerSec(1) to (2) during(10)).protocols(mobileApiProtocol)).maxDuration(100)
    .assertions(global.responseTime.max.lt(2000), global.successfulRequests.percent.gt(95))

}

Enter fullscreen mode Exit fullscreen mode

Or you can use the gatling frontline version (commercial version) in here

Alt Text

III.What is Scala:

Scala stands for scalable language which compile to java byte code.
Scala is a multiparadigm language that supports both object-oriented and functional programming style.

Alt Text

You can find out more about Scala in here

IV.Set things up:

1.IntelliJ:

For our loadtest project, we will need to use intelliJ (a product by Jetbrains) which targets supporting JVM languages.

You can download the IntelliJ from here

2.Maven:

We will use maven as a build tool for the project, so you will need to install maven.

Alt Text

Please download maven from here

After download, please set the system path to the bin directory of maven

3.Java jdk:

Please download jdk8 for stable version working with Scala and Gatling.

You can download from here

4.Scala sdk:

You can set the scala sdk directly in IntelliJ.
IntelliJ will automatically found the scala sdk version for you and suggest if you have not done so.

The Scala sdk version you should install is 2.12.10 for stability

Alt Text

V.Get your hands dirty in the real project:

1.Create a maven scala project in IntelliJ:

Go to File -> New project -> tick on create from archetype -> choose scala-archetype-simple

Alt Text

2.Pom file:

We will add dependencies for our project in the pom file.

Also, we add the mechanism so that for each profile we parse into maven commandline, it will choose the correct env config file for us.

The pom file would be like below, a little bit long (:D)

#pom file

<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>coccoc-music</groupId>
  <artifactId>mobile-api-perf</artifactId>
  <version>1.0-SNAPSHOT</version>
  <inceptionYear>2008</inceptionYear>
  <properties>
    <scala.version>2.12.10</scala.version>
    <gatling.version>3.3.1</gatling.version>
  </properties>

  <repositories>
    <repository>
      <id>scala-tools.org</id>
      <name>Scala-Tools Maven2 Repository</name>
      <url>http://scala-tools.org/repo-releases</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala-library</artifactId>
      <version>${scala.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.specs</groupId>
      <artifactId>specs</artifactId>
      <version>1.2.5</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.gatling</groupId>
      <artifactId>gatling-test-framework</artifactId>
      <version>3.3.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.gatling/gatling-core -->
    <dependency>
      <groupId>io.gatling</groupId>
      <artifactId>gatling-core</artifactId>
      <version>3.3.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.gatling.highcharts/gatling-highcharts -->
    <dependency>
      <groupId>io.gatling.highcharts</groupId>
      <artifactId>gatling-highcharts</artifactId>
      <version>3.3.1</version>
      <type>pom</type>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.gatling.highcharts/gatling-charts-highcharts -->
    <dependency>
      <groupId>io.gatling.highcharts</groupId>
      <artifactId>gatling-charts-highcharts</artifactId>
      <version>3.3.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.typesafe.play/play-json -->
    <dependency>
      <groupId>com.typesafe.play</groupId>
      <artifactId>play-json_2.12</artifactId>
      <version>2.8.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-antrun-plugin -->
    <dependency>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-antrun-plugin</artifactId>
      <version>1.8</version>
    </dependency>
  </dependencies>


  <build>
    <testSourceDirectory>src/test/scala</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>io.gatling</groupId>
        <artifactId>gatling-maven-plugin</artifactId>
        <version>3.0.5</version>
      </plugin>
    </plugins>
  </build>

  <profiles>
    <profile>
      <id>dev</id>
      <build>
        <plugins>
          <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
              <execution>
                <phase>clean</phase>
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <tasks>
                    <!-- copy file db of profile and iWealth for test environment -->
                    <delete file="src/main/resources/env.json"/>
                    <copy file="src/main/resources/env.dev.json"
                          tofile="src/main/resources/env.json"/>
                  </tasks>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
    <profile>
      <id>stag</id>
      <build>
        <plugins>
          <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
              <execution>
                <phase>clean</phase>
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <tasks>
                    <!-- copy file db of profile and iWealth for test environment -->
                    <delete file="src/main/resources/env.json"/>
                    <copy file="src/main/resources/env.stag.json"
                          tofile="src/main/resources/env.json"/>
                  </tasks>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
    <profile>
      <id>prd</id>
      <build>
        <plugins>
          <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
              <execution>
                <phase>clean</phase>
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <tasks>
                    <!-- copy file db of profile and iWealth for test environment -->
                    <delete file="src/main/resources/env.json"/>
                    <copy file="src/main/resources/env.prd.json"
                          tofile="src/main/resources/env.json"/>
                  </tasks>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

</project>


Enter fullscreen mode Exit fullscreen mode

3.Project structure:

main package:

We only define the resources file in the main package

The tree is like:

Alt Text

File env.json is the file where we store real env usage for current run
env.{env_name}.json is the files to store env for each environment
general.json is the file to store general config value

test package:

We will have the below package:

  • configs To store configuration files to get value from config file in resources folder above

For example : The code below demonstrate how to store config value to the variable and use that in our test script


package coccocMusic.configs
import coccocMusic.models.{Environments, PlaylistDetail, SongDetail}
import utils.FileUtilsInternal
import play.api.libs.json.Json


object EnvironmentConfigs {

  val fileUtilsInternal = new FileUtilsInternal

  def environment():Environments= {

    val jsonEnvironments = fileUtilsInternal.parseFileToJson(System.getProperty("user.dir") + "\\src\\main\\resources\\env.json")
    val environmentsValue = Json.fromJson[Environments](jsonEnvironments)
    environmentsValue.getOrElse(Environments).asInstanceOf[Environments]
  }

  final val COCCOCMUSIC_MOBILE_API_DOMAIN: String = environment().coccocMusic.mobile.api.domain
  final val COCCOCMUSIC_MOBILE_API_BASICAUTH_USERNAME: String = environment().coccocMusic.mobile.api.basicAuth.username
  final val COCCOCMUSIC_MOBILE_API_BASICAUTH_PASSWORD: String = environment().coccocMusic.mobile.api.basicAuth.password
  final val COCCOCMUSIC_MOBILE_API_TESTDATA_PLAYLISTDETAIL: PlaylistDetail = environment().coccocMusic.mobile.api.testData.playlistDetail
  final val COCCOCMUSIC_MOBILE_API_TESTDATA_SONGDETAIL: SongDetail = environment().coccocMusic.mobile.api.testData.songDetail
  final val COCCOCMUSIC_MOBILE_API_TESTDATA_PLAYLISTSBYCATEGORY: Int = environment().coccocMusic.mobile.api.testData.playlistsByCategory

}


Enter fullscreen mode Exit fullscreen mode
  • executions: to store our test scenario

for example the scenario for test our home screen api:


package coccocMusic.executions.homeScreen.scenarios

import coccocMusic.configs.GeneralConfigs
import coccocMusic.setup.BaseSimulation
import io.gatling.core.Predef._
import io.gatling.core.structure.ScenarioBuilder
import io.gatling.http.Predef._

class ConcurrentRequests extends BaseSimulation {

  val scn: ScenarioBuilder = scenario("Check concurrentRequests for homescreen api")
    .exec(
      http("Get playlists and songs")
        .get(GeneralConfigs.COCCOCMUSIC_MOBILE_API_HOMESCREEN)
    .check(status.is(200)))

  val fromUsers: Double = Integer.getInteger("fromUsers",1).toDouble
  val toUsers: Double = Integer.getInteger("toUsers",1).toDouble
  val duringSeconds: Integer = Integer.getInteger("duringSeconds",1)

  setUp(scn.inject(nothingFor(4),atOnceUsers(1),rampUsersPerSec(fromUsers) to (toUsers) during(duringSeconds)).protocols(mobileApiProtocol)).maxDuration(100)
    .assertions(global.responseTime.max.lt(2000), global.successfulRequests.percent.gt(95))

}


Enter fullscreen mode Exit fullscreen mode

Above we define the endpoint for our api : GeneralConfigs.COCCOCMUSIC_MOBILE_API_HOMESCREEN

Get the input fromUsers, toUsers, duringSeconds from commandline, auto set to 1 if not defined (-DfromUsers={value} -DtoUsers={value} -DduringSeconds={value}

  • models: To define models for the environments and general configurations (Remember we got json file for resources, so models make total sense)

For example like below:

package coccocMusic.models
import play.api.libs.functional.syntax._
import play.api.libs.json.{JsPath, Reads}
import play.api.libs.json._

case class Environments(coccocMusic: CoccocMusic)

case class CoccocMusic(mobile: Mobile)

case class Mobile(api: Api)

case class Api(domain: String, basicAuth: BasicAuth, testData: TestData)

case class BasicAuth(username: String, password: String)

case class TestData(playlistDetail: PlaylistDetail, songDetail: SongDetail, playlistsByCategory: Int)

case class PlaylistDetail(nhaccuatui: String, zingmp3: String)

case class SongDetail(nhaccuatui: String, zingmp3: String)

object BasicAuth{
  implicit val basicAuthReads: Reads[BasicAuth] = (
    (JsPath \ "username").read[String] and
      (JsPath \ "password").read[String]
    )(BasicAuth.apply _)
}

object TestData{
  implicit val testDataReads: Reads[TestData] = (
    (JsPath \ "playlistDetail").read[PlaylistDetail] and
      (JsPath \ "songDetail").read[SongDetail] and
      (JsPath \ "playlistsByCategory").read[Int]
  )(TestData.apply _)
}

object PlaylistDetail{
  implicit val playlistDetailReads: Reads[PlaylistDetail] = (
    (JsPath \ "nhaccuatui").read[String] and
      (JsPath \ "zingmp3").read[String]
  )(PlaylistDetail.apply _)
}

object SongDetail{
  implicit val songDetailReads: Reads[SongDetail] =(
    (JsPath \ "nhaccuatui").read[String] and
      (JsPath \ "zingmp3").read[String]
  )(SongDetail.apply _)
}

object Api{
  implicit val apiReads: Reads[Api] = (
    (JsPath \ "domain").read[String] and
      (JsPath \ "basicAuth").read[BasicAuth] and
      (JsPath \ "testData").read[TestData]
    )(Api.apply _)
}

object Mobile{

  implicit val mobileReads: Reads[Mobile] =
    (__ \ "api").read[Api].map(Mobile.apply)
}

object CoccocMusic{

  implicit val coccocMusicReads: Reads[CoccocMusic] =
    (__ \ "mobile").read[Mobile].map(CoccocMusic.apply)
}

object Environments{
  implicit val coccocMusicReads: Reads[Environments] =
    (__ \ "coccocMusic").read[CoccocMusic].map(Environments.apply)
}


Enter fullscreen mode Exit fullscreen mode
  • setup :

We will set the BaseSimulation class for after use.

Example code below for setting the domain and basic auth:


package coccocMusic.setup
import coccocMusic.configs.{EnvironmentConfigs, GeneralConfigs}
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.http.protocol.HttpProtocolBuilder

class BaseSimulation extends Simulation{


  val mobileApiProtocol: HttpProtocolBuilder = http.baseUrl(EnvironmentConfigs.COCCOCMUSIC_MOBILE_API_DOMAIN
    +GeneralConfigs.COCCOCMUSIC_MOBILE_API_PREFIX+GeneralConfigs.COCCOCMUSIC_MOBILE_API_VERSION).basicAuth(
    EnvironmentConfigs.COCCOCMUSIC_MOBILE_API_BASICAUTH_USERNAME,EnvironmentConfigs.COCCOCMUSIC_MOBILE_API_BASICAUTH_PASSWORD)


}


Enter fullscreen mode Exit fullscreen mode
  • utils: This is where we define the utility we make for our project

Example for the utils to parse the json file to json object:


package utils

import play.api.libs.json.{JsValue, Json}

import scala.io.Source

class FileUtilsInternal {

  def parseFileToJson(pathName: String): JsValue = {

    val bufferSource=Source.fromFile(pathName)
    var jsonFormat: JsValue = null
    try {
      val source:String=bufferSource.getLines.mkString
      jsonFormat = Json.parse(source)
  }
    finally {
      bufferSource.close()
    }
    jsonFormat
  }

}


Enter fullscreen mode Exit fullscreen mode
  1. Run the scenario:

For example we can run the scenario for home screen api like below:

mvn clean gatling:test -Pdev -Dgatling.simulationClass=coccocMusic.executions.homeScreen.scenarios.ConcurrentRequests -DfromUsers=10 -DtoUsers=14 -DduringSeconds=20

Let me explain in detail

mvn clean : to clean the target folder

gatling:test to run the test

-Pdev : this is to remove the content in env.json file, and parse the content from env.dev.json to env.json (You can definitely change to -Pstag or -Pprd for different environment)

-Dgatling.simulationClass=coccocMusic.executions.homeScreen.scenarios.ConcurrentRequests : this to specify the simulationClass to run the test

-DfromUsers=10 -DtoUsers=14 -DduringSeconds=20 : this is to define the number of users to users, the duration for ramping up the requests

  1. The report file:

Will look like this where you can see the details for overall or for specific request (located in target/gatling/scenario_name/index.html)

Alt Text

VI.Source code:

The source code for the project you can find here in github

That's it. I hope it helps

Peace as always!!!

Notes: If you feel this blog help you and want to show the appreciation, feel free to drop by :

This will help me to contributing more valued contents.

Happy coding!!!

Top comments (1)

Collapse
 
srkanpu profile image
srkanpu

This article is very good.. can you please add the git link it seems like it is broken