Gatling pass data from feeder to exec - scala

Could you help me how to get data from feeder in exec block? When I use exec with session input parameter is not a problem, but when I want to use with ActionBuilder I don't know how to do it. Documentation says to use Gatling EL, but seems it can be use only with http protocol. I want to use gRPC protocol, so I can't use methods http().
Do you have any idea how to do it?
My code:
def runAction : ActionBuilder = {
//TODO how to get parameter1 and parameter2 from session or feeder here.
}
def getRandomData() = Map("parameter1" -> UUID.randomUUID.toString, "parameter2" -> (Random.nextInt(100000) + 700000))
val feeder = Iterator.continually(getRandomData())
scenario("TestGRPC server")
.feed(feeder)
.exec(session => {
val parameter1= session("parameter1").as[String] //here is works
val parameter2= session("parameter2").as[Int] //here is works
println("parameter1: " + parameter1+ "parameter2: " + parameter2) //print diffrent for each execution
session
})
.exec(runAction)
setUp(
grpcScenario.inject(
constantUsersPerSec(2) during (durationInSeconds seconds))
).protocols(grpcConfig)

according to the gatling-grpc documentation, the payload can indeed take an expression.
which means you should be able to have code like this in 'runAction'
grpc("request name")
.rpc(rpc.method)
.payload("parameter1: ${parameter1}")

Related

Gatling. What would be the best way to pass feeder data to other functions?

I'm trying to write a load test for service.
I want to build DeliveryObjects and publish them, each delivery must have a unique id.
The problem I encounter is that I cant pass variables from the session to a function that I wrote (I know the documentation says I can't), also I can't "catch" the value on run time as I saw in several examples. So this is one thing I have tried:
object AdminClient extends FireClient {
def getDeliveryStateByDeliveryId(name: String = "Get delivery state by ID",
#Nullable deliveryId: Expression[String] = "${delivery_uuid}")
: HttpClientRequest = {
// The deliveryId resolve to something like this: io.gatling.core.session.el.ElCompiler$$$Lambda$372/1144897090#473b3b7a
println("delivery id in adminclient is: " + deliveryId)
get(name)
.uri(s"/url/${deliveryId}")
.requestModeAdmin
.allowOkStatus
}
}
and the scenario looks like this (to make things simpler):
object LoadTest extends FireScenarios {
val csvFeeder = csv("deliveries.csv")
fireScenario("Load test starts")(_
.feed(csvFeeder)
.exec { session =>
// Here delivery_uuid get a real value something like "b6070d6b-ce10-5fd3-b81d-ed356665f0e1"
println("delivery id id:" + session.get("delivery_uuid").as[String])
session
}
.exec(AdminClient.getDeliveryStateByDeliveryId())
)
}
So I guess my question is how can I pass a value to the var "${delivery_uuid}" in the "getDeliveryStateByDeliveryId" method?
Note that I also can't just call the getDeliveryStateByDeliveryId method from withing the
exec{ session =>
AdminClient.getDeliveryStateByDeliveryId(deliveryId = session.get("delivery_uuid"))
session
}
Although the method gets the variable as I want, the Gatling throws an error that no request was sent and no report will be produced.
I'm very confused after reading the docs too many times, any help will be much appreciated.
Let's sum up what you can find in the official documentation:
Expression[String] is a Scala type alias for (making it simple) scala.Function[Session, String]. Similarly, in Gatling's Java DSL, you directly pass java.util.Function<Session, String>.
Gatling's Scala DSL has some implicit conversion that transform String values passed to methods expecting a Function parameter into a proper function.
So if we make explicit what you've written, you actually have (doesn't compile, but you'll get the idea):
def getDeliveryStateByDeliveryId(name: String = "Get delivery state by ID",
#Nullable deliveryId: Expression[String] = session => session("delivery_uuid").as[String])
: HttpClientRequest = {
println("delivery id in adminclient is: " + deliveryId)
get(name)
.uri("/url/" + deliveryId)
.requestModeAdmin
.allowOkStatus
}
This cannot possibly work. You're concatenating a String and a Function which, like in Java uses the toString inherited from Object.
Now, as you're a beginner, why do you need deliveryId to be a function? Can't it just be a String with the name of the desired attribute?
def getDeliveryStateByDeliveryId(name: String = "Get delivery state by ID",
deliveryId: String = "delivery_uuid")
: HttpClientRequest =
get(name)
.uri(s"/url/#{$deliveryId}")
.requestModeAdmin
.allowOkStatus
object LoadTest extends FireScenarios {
val csvFeeder = csv("deliveries.csv")
fireScenario("Load test starts")(_
.feed(csvFeeder)
.exec(AdminClient.getDeliveryStateByDeliveryId())
)
}

I can't get a value from one of two feeders

I have two feeders in my test scenario
When i use one of them in first request it works alright, however when I use it in the next request block it doesn't work
I have tried changing the feed(feederName) position but still having the same problem
Here is a snippet of my test scenario with some comment to explain what's not working
//the Two feeders
val kmPerYearFeeder = Iterator.continually(
Map("kmPerYear" -> Random.shuffle(List("10000", "15000", "20000", "25000", "30000", "35000", "40000", "45000", "50000")).head)
)
val customerTypes = Iterator.continually(
Map("customerType" -> Random.shuffle(List("P","B")).head)
)
//here the customerTypes feeder is working
val homepage = feed(customerTypes)
.exec(http("homepage")
.get("/?customer_type=${customerType}"))
//this block is not really important but working alright
val pdp = exec(http("homepage")
....
// the feeder here doesn't work
val calculate_rate = feed(kmPerYearFeeder)
.exec(http("calculate_random_rate")
.get(session => session("random_pdp_link").as[String] + "?inquiry_type=&km_per_year=${kmPerYear}")
.check(status.is(200)))
val pdp_scenario = scenario("PDP").exec(homepage).exec(pdp).exec(calculate_rate)
setUp(
pdp_scenario.inject(
rampUsers(10) during (5 seconds),
).protocols(httpProtocol),
)
these are the get requests that are executed (got them from the logger)
GET ********?inquiry_type=&km_per_year=$%7BkmPerYear%7D
GET ********?inquiry_type=&km_per_year=$%7BkmPerYear%7D
GET ********?inquiry_type=&km_per_year=$%7BkmPerYear%7D
Your issue is that in where you attempt to use the Gatling EL to reference the kmPerYear session var in
.get(session => session("random_pdp_link").as[String] + "?inquiry_type=&km_per_year=${kmPerYear}")
this version of .get takes a session function (which you are using to get "random_pdp_link"), but the Gatling EL doesn't work in session functions.
You either need to get it manually using
session("kmPerYear").as[String]
or to reference "random_pdp_link" via the EL and not use the session function get. eg:
.get("${random_pdp_link}?inquiry_type=&km_per_year=${kmPerYear}")

Gatling Need to run next scenario if previous scenario is passed using doIf

i am new to Scala and gatling. i need to run scenaio if previous scenario is passed using doIf.
My code is:
HttpRequest
object CompanyProfileRequest {
val check_company_profile: HttpRequestBuilder = http("Create Company
Profile")
.get(onboarding_url_perf + "/profile")
.headers(basic_headers)
.headers(auth_headers)
.check(status.is(404).saveAs("NOT_FOUND"))
val create_company_profile: HttpRequestBuilder = http("Create Company
Profile")
.post(onboarding_url_perf + "/profile")
.headers(basic_headers)
.headers(auth_headers)
.body(RawFileBody("data/company/company_profile_corporation.json")).asJson
.check(status.is(200))
.check(jsonPath("$.id").saveAs("id"))
}
Scenario class is:-
object ProfileScenarios {
val createProfileScenarios: ScenarioBuilder = scenario("Create profile
Scenario")
.exec(TokenScenario.getCompanyUsersGwtToken)
.exec(CompanyProfileRequest.check_company_profile)
.doIf(session => session.attributes.contains("NOT_FOUND")) {
exec(CompanyProfileRequest.create_company_profile).exitHereIfFailed
}
}
And Simulation is :-
private val createProfile = ProfileScenarios
.createProfileScenarios
.inject(constantUsersPerSec(1) during (Integer.getInteger("ramp", 1)
second))
setUp(createProfile.protocols(httpConf))
Whenever i am running this simulation, I am not able to check this condition:-
.doIf(session => session.attributes.contains("NOT_FOUND"))
Any help is much appreciated.
Regards,
Vikram
I was able to get your example to work, but here's a better way...
the main issue with using
.check(status.is(404).saveAs("NOT_FOUND"))
and
.doIf(session => session.attributes.contains("NOT_FOUND"))
to implement conditional switching is that you've now got a check that will cause check_company_profile to fail when it really shouldn't (when you get a 200, for example).
A nicer way is to use a check transform to insert a boolean value into the "NOT_FOUND" variable. This way, your check_company_profile action can still pass when the office exists, and the doIf construct can just use the EL syntax and be much clearer as to why it's executing.
val check_company_profile: HttpRequestBuilder = http("Create Company Profile")
.get(onboarding_url_perf + "/profile")
.headers(basic_headers)
.headers(auth_headers)
.check(
status.in(200, 404), //both statuses are valid for this request
status.transform( status => 404.equals(status) ).saveAs("OFFICE_NOT_FOUND") //if the office does not exist, set a boolean flag in the session
)
now that you've got a boolean session variable ("OFFICE_NOT_FOUND") you can just use that in your doIf...
.doIf("${OFFICE_NOT_FOUND}") {
exec(CompanyProfileRequest.create_company_profile).exitHereIfFailed
}

Consuming a service using WS in Play

I was hoping someone can briefly go over the various ways of consuming a service (this one just returns a string, normally it would be JSON but I just want to understand the concepts here).
My service:
def ping = Action {
Ok("pong")
}
Now in my Play (2.3.x) application, I want to call my client and display the response.
When working with Futures, I want to display the value.
I am a bit confused, what are all the ways I could call this method i.e. there are some ways I see that use Success/Failure,
val futureResponse: Future[String] = WS.url(url + "/ping").get().map { response =>
response.body
}
var resp = ""
futureResponse.onComplete {
case Success(str) => {
Logger.trace(s"future success $str")
resp = str
}
case Failure(ex) => {
Logger.trace(s"future failed")
resp = ex.toString
}
}
Ok(resp)
I can see the trace in STDOUT for success/failure, but my controller action just returns "" to my browser.
I understand that this is because it returns a FUTURE and my action finishes before the future returns.
How can I force it to wait?
What options do I have with error handling?
If you really want to block until feature is completed look at the Future.ready() and Future.result() methods. But you shouldn't.
The point about Future is that you can tell it how to use the result once it arrived, and then go on, no blocks required.
Future can be the result of an Action, in this case framework takes care of it:
def index = Action.async {
WS.url(url + "/ping").get()
.map(response => Ok("Got result: " + response.body))
}
Look at the documentation, it describes the topic very well.
As for the error-handling, you can use Future.recover() method. You should tell it what to return in case of error and it gives you new Future that you should return from your action.
def index = Action.async {
WS.url(url + "/ping").get()
.map(response => Ok("Got result: " + response.body))
.recover{ case e: Exception => InternalServerError(e.getMessage) }
}
So the basic way you consume service is to get result Future, transform it in the way you want by using monadic methods(the methods that return new transformed Future, like map, recover, etc..) and return it as a result of an Action.
You may want to look at Play 2.2 -Scala - How to chain Futures in Controller Action and Dealing with failed futures questions.

Using Streams in Gatling repeat blocks

I've come across the following code in a Gatling scenario (modified for brevity/privacy):
val scn = scenario("X")
.repeat(numberOfLoops, "loopName") {
exec((session : Session) => {
val loopCounter = session.getTypedAttribute[Int]("loopName")
session.setAttribute("xmlInput", createXml(loopCounter))
})
.exec(
http("X")
.post("/rest/url")
.headers(headers)
.body("${xmlInput}"))
)
}
It's naming the loop in the repeat block, getting that out of the session and using it to create a unique input XML. It then sticks that XML back into the session and extracts it again when posting it.
I would like to do away with the need to name the loop iterator and accessing the session.
Ideally I'd like to use a Stream to generate the XML.
But Gatling controls the looping and I can't recurse. Do I need to compromise, or can I use Gatling in a functional way (without vars or accessing the session)?
As I see it, neither numberOfLoops nor createXml seem to depend on anything user related that would have been stored in the session, so the loop could be resolved at build time, not at runtime.
import com.excilys.ebi.gatling.core.structure.ChainBuilder
def addXmlPost(chain: ChainBuilder, i: Int) =
chain.exec(
http("X")
.post("/rest/url")
.headers(headers)
.body(createXml(i))
)
def addXmlPostLoop(chain: ChainBuilder): ChainBuilder =
(0 until numberOfLoops).foldLeft(chain)(addXmlPost)
Cheers,
Stéphane
PS: The preferred way to ask something about Gatling is our Google Group: https://groups.google.com/forum/#!forum/gatling