Getting to grips with Gatling: Custom Feeder

About a year ago someone on my current project did an initial setup of Gatling. This is a performance/stress test tool written in Scala, it is easy to use but very powerful. The basic idea is that you write the test scenario’s in code, the same way as you would create integration/unit tests. The advantage is that the resulting tests are easy to expand, maintain and you’ve got everything under version control.

One very powerful feature Gatling has is the Recorder. It is an application you can launch and it acts like a proxy server. Being a proxy server it can record and log all the calls you do to an application. Without writing any code it can automatically create a stress test!

But in my case someone has already created a scenario for me.

Basic Example

As a programmer it can be a bit daunting when you encounter a ‘foreign language’, in this case Scala. Fear not though, it is very easy to read, extend and write Gatling tests. This is the scenario I started with today:

class OurSimulation extends Simulation {

  // Define some basic HTTP protocol settings:
  val baseHttpProtocol = http
    .inferHtmlResources()
    .acceptHeader( """*/*""")
    .acceptEncodingHeader( """gzip,deflate""")
    .acceptLanguageHeader( """nl-NL,nl;q=0.8,en-US;q=0.6,en;q=0.4""")
    .contentTypeHeader( """text/xml""")
    .userAgentHeader( """Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36""")

  // Define a variables that is inserted into the SOAP template:
  val incrementalId = new AtomicInteger

  // Define the scenario, read from CSV file, add additional variable to the session and post to OurEndpoint:
  val scn = scenario("Our scenario")
    .feed(csv("data.csv").circular)
    .exec(session => session.set("idField", incrementalId.getAndIncrement))
    .exec(OurEndpoint.post("SoapMessageTemplate.txt"))
}

object OurEndpoint {

  def post(soapMessage: String) = exec(http("RequestName")
      .post("""/ourapplication/webservices/OurSoapEndpoint""")
      .body(ELFileBody(soapMessage))
      .check(status.is(200))
    )
}

class OurSimulationLocal extends OurSimulation {

  // The instance we want to run, against localhost (we also have these for other servers)
  val httpProtocol = baseHttpProtocol.baseURL("http://localhost:1234")

  // Setup the test, ramp up 10 users over a period of 20 seconds
  setUp(scn.inject(rampUsers(10) over (20 seconds))).protocols(httpProtocol)
}

The SoapMessageTemplate.txt is just a plain text file containing one SOAP call, instead of data it contains tags like: ${field1}. There is also data.csv which is a CSV file with the field names in the first row, comma separated values in the following rows. The tags in the SOAP call get replaced by the values from the selected CSV row.

There is probably nothing more I need to explain, the code reads like it should. The main startingpoint is OurSimulationLocal, this class sets up the Simulation (using setUp()). The Simulation is contructed in the OurSimulation class, for each call it takes the ‘next’ value from the CSV, adds one additional field and posts the message to OurEndpoint, finally it checks for a 200 reply.

Custom Feeder

Instead of having a big list of different options in a CSV file, my task for today was to make a custom Feeder. It is the class that provides data as input for the SOAP messages. We want to have some random data that still makes sense to the application, based on some averages.

It looked pretty hard to do, not having programmed a lot of Scala. In the end it was a breeze! The only thing that bugs me is that there are so many different ways to write your code in Scala. I’m not experienced enough yet to know if it ‘reads’ like proper Scala code, I don’t yet know what ‘good’ and ‘bad’ code looks like.

object MyFeeder {

  val random = new Random()

  def apply():  Feeder[String] = {
    Iterator.continually(Map(
      ("name", Random.alphanumeric.take(5).mkString.toUpperCase),
      ("country", (if (random.nextDouble() <= percentageBuitenlands) "B" else "NL"))
    )
  }
}

class OurSimulation extends Simulation {

  //... snip ....

  // The same scenario, now using our own feeder:
  val feeder = MyFeeder()

  val scn = scenario("Our scenario")
    .feed(feeder)
    .exec(session => session.set("idField", incrementalId.getAndIncrement))
    .exec(OurEndpoint.post("SoapMessageTemplate.txt"))
}

Writing a custom feeder is pretty easy to do. The apply() function creates an instance of a Feeder[String] which in turn returns a Map containing [String, String] key value pairs. These are then used to fill the SOAP template!

Of course the real thing I build today is a bit larger and more complex, but the code is basically the same!