Custom actions not able to execute in parallel?

Problem: When creating custom actions with Action Builder, the execute methods are being run sequentially resulting in a max TPS < 10. This low of TPS is not caused by network, cpu, or memory limitations as far as I can see (CPU remains at 5% or less throughout the execution, memory remains low, network utilization is insignificant). In order to achieve load tests at the scale we want, we need to be able to fully utilize the resources we have and execute these custom actions in parallel.

Background / Question: My team is trying to use Gatling to test one of our services. This service utilizes Thrift - which I’ve seen is not natively supported. After reading through many docs, I stumbled upon various suggestions from Stephanie and other developers that said that people should make a custom action builder.

Following the advice, we created a new action in the simulation like this:

`

val readLabelsBuilder = new ActionBuilder {
  def build(next: ActorRef, protocols: Protocols) = {
    system.actorOf(Props(new ReadLabelsAction(next)))
  }
}

`

I then built up a scenario and executed my action:

`

val scn = scenario("Test simulation thingy")
  .exec(readLabelsBuilder)

`

The ReadLabelsAction class is defined similar to the one below. In this instance, though, I took out everything relating to Thrift to demonstrate that it’s not a Thrift issue but an issue with the underlying execute method:

`

import akka.actor.ActorRef
import io.gatling.core.Predef._
import io.gatling.core.action.Chainable
import io.gatling.core.result.message.{KO, OK}
import io.gatling.core.result.writer.DataWriterClient
import io.gatling.core.session.Session
import io.gatling.core.util.TimeHelper._

class ReadLabelsAction(val next: ActorRef) extends Chainable with DataWriterClient {
  // Var == variables
  var errorMessage : Option[String] = None
  var status: Status = OK
  var start: Long = 0
  var end: Long = 0

  def execute(session: Session) {
    try {
      start = nowMillis

      Thread sleep 100

      end = nowMillis
    } catch {
      case e: Exception =>
        errorMessage = Some(e.getMessage)
        logger.error("Client call failed", e)
        status = KO
    } finally {
      writeRequestData(session,
        requestName,
        requestStartDate = start,
        requestEndDate = end,
        responseStartDate = start,
        responseEndDate = end,
        status,
        errorMessage,
        extraInfo = Nil)

      // Pass the focus onto the next action in the chain
      next ! session
    }
  }}

`

Unfortunately, this execute method is always run sequentially. 1000 users that each sleep for 100ms took just over 1.5 minutes to complete with an average rpm of 9.832. If this was processing in parallel, this should have been done magnitudes more quickly.

Things I’ve tried:
In the simulation, I’ve tried:

  • Dumping in thousands of users at once

  • Utilizing the throttle method to specify the rpm I want

  • Ramping users up slowly over time

  • Creating the client outside of the session

  • Creating the client in the session

  • Doing just an http call instead of a custom action builder (the generic http call works perfectly and in parallel).
    In the action itself I’ve tried:

  • Moving the next ! session to the top of the try block (to perhaps allow things to continue before they’re done being executed here)

  • Creating the client in the execute method so that it doesn’t have to utilize the session

  • Removing all thrift code and simply doing a sleep for 100 ms so I could see if the method was run in parallel or not (as I posted above)

Question: Am I missing something here? I’m not sure if this is a Gatling issue, an Akka issue, or something else entirely. I feel like this should totally be possible but I haven’t figured out what I’m doing wrong. Any help would be greatly appreciated!!

Oh and sorry for not mentioning this but I’m using the latest version of Gatling (2.1.7).

Hi Mike,

In random order:

  • Measuring blocking calls is not a great fit in Gatling: you’re blocking the current thread, making it unavailable for running other users. So your sample with Thread.sleep is very wrong, and I hope you don’t do the same thing with Thrift. What you do in your finally block should be performed in a callback.
  • Throttle is a rate limiter.
  • Yes, Actions are chained sequentially. Actions represent a step in a process, whose duration you want to measure. Plus, you also want to be able to capture some response elements to be able to craft the next requests. You never described what duration you’re trying to measure and how you plan your load tests to work.
  • I’m pretty sure you want to create one single share thrift client, not one per virtual user.
  • My name’s Stéphane (male, while Stephanie is female).

So right now I believe we’re using thrift with a standard http client. It sounds like we should try to get it working with an async http client (http://stackoverflow.com/q/28673154).

Would we then need a separate action for sending vs receiving the response? Are there any examples we could follow that demonstrate how to split this up?

Thanks!