Handle ramp duration parameter that may be zero or nonzero

Hi all,

I have build job that calls my gatling simulation file and sets some configuration parameters like ramp duration, peak TPS, and steady state duration. Sometimes the job is kicked off with a ramp duration specified, but other times, ramp duration is zero. I am wondering what the best way is to maintain a single file that can handle both zero and nonzero ramp duration.

In other words, I am trying to do a ramp only if durationRampupInMinutes is specified as non-zero, followed by steady state for durationSteadyStateInMinutes always.

First, here was my original attempt - “if” condition inside a scenario.inject(). But it seems to be skipping over the “if” part, even when ramp time is specified.

setUp(
callChainFlow.inject(
{
if (durationRampupInMinutes != 0) {
rampUsersPerSec(0) to constUsersPerSec during (durationRampupInMinutes minutes)
}
constantUsersPerSec(constUsersPerSec) during (durationSteadyStateInMinutes minutes)
}
)
).protocols(http)

Also tried hard coding if (true) but even that does not trigger.

Second, here is alternate form that works (but is more verbose and has code duplication):

if (durationRampupInMinutes != 0) {
setUp(
callChainFlow.inject(
rampUsersPerSec(0) to constUsersPerSec during (durationRampupInMinutes minutes),
constantUsersPerSec(constUsersPerSec) during (durationSteadyStateInMinutes minutes)
)
).protocols(http)
} else {
setUp(
callChainFlow.inject(
constantUsersPerSec(constUsersPerSec) during (durationSteadyStateInMinutes minutes)
)
).protocols(http)
}

Finally, here what I settled on - a way to simulate zero with a very small value:

// If ramp is zero, set to some value so rampUsersPerSec can pass, but essentially simulate zero.
var nonZeroRampInMinutes = 0.00001
if (durationRampupInMinutes != 0) {
nonZeroRampInMinutes = durationRampupInMinutes
}

setUp(
callChainFlow.inject(
rampUsersPerSec(0) to constUsersPerSec during (nonZeroRampInMinutes minutes),
constantUsersPerSec(constUsersPerSec) during (durationSteadyStateInMinutes minutes)
)
).protocols(http)

Is there a better way?

Did something change between Gatling 2.2.3 and Gatling 3.2.0 in terms of rampUsersPerSec supporting small durations? The third approach above works for me in Gatling 2.2.3. But in Gatling 3.2.0, the same code produces this error:

21:30:47.088 [ERROR] a.a.OneForOneStrategy - Division by zero
java.lang.ArithmeticException: Division by zero
at java.math.BigDecimal.divide(BigDecimal.java:1742)
at scala.math.BigDecimal.$div(BigDecimal.scala:555)
at io.gatling.core.controller.inject.open.RampRateOpenInjection.chain(OpenInjectionStep.scala:200)
at io.gatling.core.controller.inject.open.UserStream$.$anonfun$apply$1(UserStream.scala:31)
at scala.collection.immutable.List.$anonfun$foldRight$1(List.scala:408)
at scala.collection.immutable.List.foldRight(List.scala:89)
at io.gatling.core.controller.inject.open.UserStream$.apply(UserStream.scala:31)
at io.gatling.core.controller.inject.open.OpenInjectionProfile.workload(OpenInjectionProfile.scala:39)
at io.gatling.core.controller.inject.Injector$$anonfun$1.$anonfun$applyOrElse$1(Injector.scala:88)
at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:237)
at scala.collection.immutable.List.foreach(List.scala:392)
at scala.collection.TraversableLike.map(TraversableLike.scala:237)
at scala.collection.TraversableLike.map$(TraversableLike.scala:230)
at scala.collection.immutable.List.map(List.scala:298)
at io.gatling.core.controller.inject.Injector$$anonfun$1.applyOrElse(Injector.scala:89)
at io.gatling.core.controller.inject.Injector$$anonfun$1.applyOrElse(Injector.scala:82)
at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:38)
at akka.actor.FSM.processEvent(FSM.scala:707)
at akka.actor.FSM.processEvent$(FSM.scala:704)
at io.gatling.core.controller.inject.InjectorFSM.processEvent(InjectorFSM.scala:37)
at akka.actor.FSM.akka$actor$FSM$$processMsg(FSM.scala:701)
at akka.actor.FSM$$anonfun$receive$1.applyOrElse(FSM.scala:695)
at akka.actor.Actor.aroundReceive(Actor.scala:539)
at akka.actor.Actor.aroundReceive$(Actor.scala:537)
at io.gatling.core.akka.BaseActor.aroundReceive(BaseActor.scala:25)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:612)
at akka.actor.ActorCell.invoke(ActorCell.scala:581)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:268)
at akka.dispatch.Mailbox.run(Mailbox.scala:229)
at akka.dispatch.Mailbox.exec(Mailbox.scala:241)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

Thanks,
Richard

setUp(
callChainFlow.inject(
if (durationRampupInMinutes != 0) {
rampUsersPerSec(0) to constUsersPerSec during (durationRampupInMinutes minutes)
} else {
constantUsersPerSec(constUsersPerSec) during (durationSteadyStateInMinutes minutes)
}
)
).protocols(http)

Hi Stéphane,

Thanks for the reply. I am trying to have an optional ramp time if ramp duration is nonzero. But I expect the steady state duration to happen always. So, something like this then?

setUp(
  callChainFlow.inject(
    if (durationRampupInMinutes != 0) {
      rampUsersPerSec(rampStartTps) to constUsersPerSec during (durationRampupInMinutes minutes)
      constantUsersPerSec(constUsersPerSec) during (durationSteadyStateInMinutes minutes)
    } else {
      constantUsersPerSec(constUsersPerSec) during (durationSteadyStateInMinutes minutes)
    }
  )
).protocols(http)

Except when I try that, it seems that only constantUsersPerSec gets executed, not the rampUsersPerSec.

Thanks,

Richard

The if/then block has two statements. The block is being interpreted as a statement, and returning the last return value, which is why only the constantUsersPerSec is being interpreted. Try this instead:

setUp(
callChainFlow.inject(
if ( durationRampupInMinutes > 0 ) {
rampUsersPerSec( rampStartTps ) to constUsersPerSec during ( durationRampupInMinutes minutes )
} else {
nothingFor( 1 second ) // have to have some kind of placeholder for when there is no ramp
}, // Notice the comma
constantUsersPerSec( constUsersPerSec ) during ( durationSteadyStateInMinutes minutes )
)
)
.protocols(http)

Alternatively, you could simplify it like this, where you convert the ramp time to seconds, and then set a minimum ramp time of 1 second.

val rampTime = durationRampupInMinutes * 60
if ( rampTime < 1 ) rampTime = 1
setUp(

callChainFlow.inject(
rampUsersPerSec( rampStartTps ) to constUsersPerSec during ( rampTime seconds ),

constantUsersPerSec( constUsersPerSec ) during ( durationSteadyStateInMinutes minutes )

)
)
.protocols(http)

Hi John,

Thanks for the explanations and suggestions! I was able to solve my problem by using your first option - an else block with a do-nothing placeholder, followed by a comma. I liked this option better because it is more explicit about checking for 0 and the total requests are exactly as expected.

Second approach would work as well, and similar to what I had before in Gatling 2 as 0.00001 minutes. But I guess that number is now too low (close to 0) for Gatling 3. Setting ramp time to 1 second enables simulation to run, but if peak TPS is high enough, there are few extra requests generated, even at 1 second.

Thanks,
Richard