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!!