Techconative Logo

 Load Testing gRPC services with Gatling

Mon Jul 03 2023

gRPC  |  Load Tests  |  Gatling  |  Scala  
Load Testing gRPC services with Gatlingimage

Context

gRPC is a modern open source high performance Remote Procedure Call (RPC) that can efficiently connect services and can run better than REST services. We had an application built using gRPC services and wanted to perform load testing to get an idea about its performance under load.

For Load Testing REST API based applications we have an array of options but when it comes to testing gRPC we have limited options and here, we picked Gatling. Even with Gatling, testing gRPC service is not straight-forward, with the help of the gatling-grpc plugin we were able to perform load tests for the planned scenarios. Along with this plugin, we have a quickstart guide from the plugin developer that gives an overview of building our first gRPC test with Gatling Scala using sbt build tool.

With this blog, we will share some general and Scala specific gotchas that you may get into while building the tests.

Before Getting Started

Have a look into below points, before getting started with gRPC - Gatling tests.

'before-start'

  • At first, look into gRPC, Protocol Buffers and understand your gRPC service function, get familiar with your service .proto file.

  • Try to hit the gRPC service manually with BloomRPC, Insomnia or any other tools and understand the parameters required, to get the desired output.

  • Suggest you to pick a familiar programming language (Scala, Java, Kotlin) supported by Gatling, so that it helps with building your gRPC tests.

  • Going to use Scala (as we did), check sbt scala build tool and ScalaPB compiler plugin to generate target classes for the .proto file.

  • In general, it’s recommended to explore the methods within the generated target classes (Request, Response,ServiceGrpc,..) for the .proto file and understand the types used in method params.

Gotchas with your gRPC tests

Payload

From this article, you will get a basic gRPC test with gatling as below by passing an empty payload defaultInstance and it works fine. Test gets passed!

grpc("request_1") .rpc(HealthGrpc.METHOD_CHECK) .payload(HealthCheckRequest.defaultInstance)

Ideally, this cannot be your scenario, we need to pass a payload to the request and at times we may need to handle a complex payload too.

Let’s see how to build your payload step by step:

'started'

  • With compile cmd executed, we get the target classes generated for the .proto file.
  • Go to target/scala-2.13/src_managed, check for *Request class file (that represents the request that we’re intended to test) and import that method to your test within the payload().
  • After importing that method, we need to follow its definition and pass the required parameters.

Scala Types

For passing the parameters we need to check the type used, and with Scala we would like to share a few, as below: (For more info. on Scala types, check here)

  • Option - Represents optional values. Instances of Option are either an instance of Some or the object None.
  • Seq - is an interface for sequences, representing an ordered collection of elements.

Examples for the aforementioned types are below, first for the Option type we need to pass it within Some("value"/anotherRefMthd) or None and here we have used Some() to pass the value.

.payload(RelationsRequest( query = Some(Query()), relationQuery = Some(RelationQuery( namespace = Some("TechConative"), `object` = Some("FormHouse.Pro") )), expandMask = Some(com.google.protobuf.field_mask.FieldMask()), pageSize=0 ))

Breaking down the above request method, it is a simple method of params

  • RelationsRequest(query, relationQuery, expandMask, pageSize) and for relationQuery - we are referring to another method with params
  • RelationQuery(namespace,object)

'connecting'

Moving on to the next type Seq with an example below, here we are passing multiple values in a sequence to process this request as a whole within this method and other things are the same as above referring to another method/using Some.

.payload(TransactRelationsRequest( Seq( RelationDelta( action = RelationDelta.Action.ACTION_INSERT, relationTuple = Some( RelationTuple( namespace = "TechConative", `object` = "FormHouse.Pro" subject = Some( Subject(Subject.Ref.Set(SubjectSet( namespace = "QE", `object` = "Performance" ))))) ))))

CSV feeders

We are good with building/passing payload to a request regardless of its type, it works fine - Test Passed! But we are into Load Testing, need to pass xxx multiple requests and it can’t be of static data/payload.

'load'

For a Dynamic payload, one of the most popular ways is having csv files with all required data and referring it within tests. From Gatling we have CSV Feeders with different strategies built-in and we can have that in gRPC tests as well.

Usually ${csvHeader} or #{csvHeader} is used to pass reference but for gRPC payload, it is slightly different. We need to get the values from session and an example to follow:

val csvFeeder = csv("data/tech.csv").circular val resourceRequest = grpc("${techType}"+":"+"#{techName}") .rpc(ReadServiceGrpc.METHOD_LIST_RELATIONS) .payload( session => RelationsRequest( query = Some(Query()), relationQuery = Some(RelationQuery( namespace = Some(session("techType").as[String]), `object` = Some(session("techName").as[String]) )), expandMask = Some(com.google.protobuf.field_mask.FieldMask()), pageSize = 0 )) val scn = scenario("For the resource, list all users") .feed(csvFeeder) .exec(resourceRequest)

In the payload we need to use session("csvHeader").as[String] to get the values from the CSV file and we got our Dynamic Payload for Load Tests.

Checks

To verify/validate the response, tests should have a check (or multiple checks).

'check'

For that, adding .check() to our gRPC request as below, does the verification/validation. And .extract() will extract the response and that can be saved to pass/refer to another subsequent request from the session.

val request = grpc("ListNames") .rpc(NamesServiceGrpc.METHOD_LIST_NAMES) .payload(NamesRequest.defaultInstance) .extract(_.names.some)(_ saveAs "names") .check(statusCode is Status.Code.OK) val scn = scenario("ListNames_Once_Trial_Run") .exec(request) .exec(session => { val names = session("names").as[String] println(s"***Response***: $names") session })

Appendix

For setup, refer to plugin developer quickstart guide, to have the base setup or you can also refer below steps for Gatling - Scala - sbt project setup.

'setup'

In case of any issues with setup, try below things that helped us:

  • Check for the versions of sbt / scala / scalapb / gatling
  • Reload project or Re-open IDE in case of any changes made

Additional things we did as a best practice along with the setup is having an application.conf file to isolate the ipAddress/Port details and also added an utils package to keep the common code for loading the conf file and getting the values.

We would love to hear from you! Reach us @

info@techconative.com

Techconative Logo

More than software development, our product engineering services goes beyond backlog and emphasizes best outcomes and experiences.