gRPC Protocol

Learn about gRPC protocol settings, TLS, and load balancing

Bootstrapping

The Gatling gRPC DSL is not imported by default.

You have to manually add the following imports:

     
import io.gatling.javaapi.grpc.*;

import static io.gatling.javaapi.grpc.GrpcDsl.*;
import io.gatling.javaapi.grpc.*

import io.gatling.javaapi.grpc.GrpcDsl.*
import io.gatling.grpc.Predef._

Protocol

Use the grpc object in order to create a gRPC protocol.

As with every protocol in Gatling, the gRPC protocol can be configured for a scenario. This is done thanks to the following statements:

     
GrpcProtocolBuilder grpcProtocol = grpc
  .forAddress("host", 50051);

ScenarioBuilder scn = scenario("Scenario"); // etc.

setUp(
  scn.injectOpen(atOnceUsers(1))
    .protocols(grpcProtocol)
);
val grpcProtocol = grpc
  .forAddress("host", 50051)

val scn = scenario("Scenario") // etc.

setUp(
  scn.injectOpen(atOnceUsers(1))
    .protocols(grpcProtocol)
)
val grpcProtocol = grpc
  .forAddress("host", 50051)

val scn = scenario("Scenario") // etc.

setUp(
  scn.inject(atOnceUsers(1))
    .protocols(grpcProtocol)
)

Target required

The first and only mandatory step is to configure the remote service address and port.

forAddress

It can be done with either the address as host and port.

     
grpc.forAddress("host", 50051);
grpc.forAddress("host", 50051)
grpc.forAddress("host", 50051)
forTarget

Or by using a URL or FDQN with both host and port directly:

     
grpc.forTarget("dns:///host:50051");
grpc.forTarget("dns:///host:50051")
grpc.forTarget("dns:///host:50051")

Channel options

By default, every user will use their own gRPC channel to connect to the remote service.

shareChannel

It is possible to share the same channel for all users by using:

     
grpc.shareChannel();
grpc.shareChannel()
grpc.shareChannel

Be aware that even though the same channel is used for all users, the number of underlying connections used doesn’t scale automatically.

useChannelPool

Use a pool of gRPC channels, instead of a single channel. This allows opening more underlying HTTP/2 connections. Useful when using shareChannel and when performance is limited by the maximum number of concurrent gRPC streams open on each connection.

     
grpc.useChannelPool(4);
grpc.useChannelPool(4)
grpc.useChannelPool(4)

Encryption and authentication

It is possible to use either unencrypted or encrypted connections to a remote service.

usePlaintext

If you don’t want the connection to be encrypted:

     
grpc.usePlaintext();
grpc.usePlaintext()
grpc.usePlaintext
useInsecureTrustManager default

If you want to trust all certificates without any verification, you can use an insecure trust manager. A useful option for self-signed certificates:

     
grpc.useInsecureTrustManager();
grpc.useInsecureTrustManager()
grpc.useInsecureTrustManager

This is the default option as it is more performant and validating certificates typically isn’t important for load tests.

useStandardTrustManager

Finally, you can use the standard trust manager that comes with the JVM:

     
grpc.useStandardTrustManager();
grpc.useStandardTrustManager()
grpc.useStandardTrustManager
useCustomCertificateTrustManager

Or use your own certificate:

     
grpc.useCustomCertificateTrustManager("certificatePath");
grpc.useCustomCertificateTrustManager("certificatePath")
grpc.useCustomCertificateTrustManager("certificatePath")
shareSslContext

TLS handshake will be performed only once and the TLS sessions will be shared between all the users. Use this option if you want to avoid the overhead of TLS while still having per-user channels.

     
grpc.shareSslContext();
grpc.shareSslContext()
grpc.shareSslContext

callCredentials

You can specify call credentials by providing an instance of io.grpc.CallCredentials. This will be applied to each gRPC call, except when overridden on specific calls.

     
grpc
  // with a constant
  .callCredentials(callCredentials)
  // or with an EL string to retrieve CallCredentials already stored in the session
  .callCredentials("#{callCredentials}")
  // or with a function
  .callCredentials(session -> {
    var name = session.getString("myUserName");
    return callCredentialsForUser(name);
  });
grpc
  // with a constant
  .callCredentials(callCredentials)
  // or with an EL string to retrieve CallCredentials already stored in the session
  .callCredentials("#{callCredentials}")
  // or with a function
  .callCredentials { session ->
    val name = session.getString("myUserName")!!
    callCredentialsForUser(name)
  }
grpc
  // with a constant
  .callCredentials(callCredentials)
  // or with an EL string to retrieve CallCredentials already stored in the session
  .callCredentials("#{callCredentials}")
  // or with a function
  .callCredentials { session =>
    val name = session("myUserName").as[String]
    callCredentialsForUser(name)
  }

channelCredentials

You can specify channel credentials by providing an instance of io.grpc.ChannelCredentials.

     
grpc
  // with a constant
  .channelCredentials(channelCredentials)
  // or with an EL string to retrieve CallCredentials already stored in the session
  .channelCredentials("#{channelCredentials}")
  // or with a function
  .channelCredentials(session -> {
    var name = session.getString("myUserName");
    return channelCredentialsForUser(name);
  });
grpc
  // with a constant
  .channelCredentials(channelCredentials)
  // or with an EL string to retrieve CallCredentials already stored in the session
  .channelCredentials("#{channelCredentials}")
  // or with a function
  .channelCredentials { session ->
    val name = session.getString("myUserName")!!
    channelCredentialsForUser(name)
  }
grpc
  // with a constant
  .channelCredentials(channelCredentials)
  // or with an EL string to retrieve CallCredentials already stored in the session
  .channelCredentials("#{channelCredentials}")
  // or with a function
  .channelCredentials { session =>
    val name = session("myUserName").as[String]
    channelCredentialsForUser(name)
  }

This is most often used for mutual auth TLS, for instance:

     
grpc.channelCredentials(
  TlsChannelCredentials.newBuilder()
    .keyManager(
      ClassLoader.getSystemResourceAsStream("client.crt"),
      ClassLoader.getSystemResourceAsStream("client.key"))
    .trustManager(ClassLoader.getSystemResourceAsStream("ca.crt"))
    .build()
);
grpc.channelCredentials(
  TlsChannelCredentials.newBuilder()
    .keyManager(
      ClassLoader.getSystemResourceAsStream("client.crt"),
      ClassLoader.getSystemResourceAsStream("client.key"))
    .trustManager(ClassLoader.getSystemResourceAsStream("ca.crt"))
    .build()
)
grpc.channelCredentials(
  TlsChannelCredentials.newBuilder()
    .keyManager(
      ClassLoader.getSystemResourceAsStream("client.crt"),
      ClassLoader.getSystemResourceAsStream("client.key"))
    .trustManager(ClassLoader.getSystemResourceAsStream("ca.crt"))
    .build()
)

overrideAuthority

You can override the authority used with TLS and HTTP virtual hosting.

     
grpc.overrideAuthority("test.example.com");
grpc.overrideAuthority("test.example.com")
grpc.overrideAuthority("test.example.com")

Headers

Define gRPC headers to be set on all requests. Note that keys in gRPC headers are allowed to be associated with more than one value, so adding the same key a second time will simply add a second value, not replace the first one.

asciiHeader

Shortcut for a single header with the default ASCII marshaller, i.e. io.grpc.Metadata#ASCII_STRING_MARSHALLER:

     
grpc
  // with a static header value
  .asciiHeader("key").value("value")
  // with a Gatling EL string header value
  .asciiHeader("key").valueEl("#{headerValue}")
  // with a function value
  .asciiHeader("key").value(session -> session.getString("headerValue"));
grpc
  // with a static header value
  .asciiHeader("key").value("value")
  // with a Gatling EL string header value
  .asciiHeader("key").valueEl("#{headerValue}")
  // with a function value
  .asciiHeader("key").value { session -> session.getString("headerValue") }
grpc
  // with a static header value
  .asciiHeader("key")("value")
  // with a Gatling EL string header value
  .asciiHeader("key")("#{headerValue}")
  // with a function value
  .asciiHeader("key")(session => session("headerValue").as[String])
asciiHeaders

Shortcut for multiple headers with the default ASCII marshaller as a map of multiple key and value pairs, i.e. io.grpc.Metadata#ASCII_STRING_MARSHALLER:

     
grpc.asciiHeaders(Map.of("key", "value"));
grpc.asciiHeaders(
  mapOf("key" to "value")
)
grpc.asciiHeaders(
  Map("key" -> "value")
)
binaryHeader

Shortcut for a single header with the default binary marshaller, i.e. io.grpc.Metadata#BINARY_BYTE_MARSHALLER:

     
grpc
  // with a static header value
  .binaryHeader("key").value("value".getBytes(UTF_8))
  // with a Gatling EL string header value
  .binaryHeader("key").valueEl("#{headerValue}")
  // with a function value
  .binaryHeader("key").value(session -> session.get("headerValue"));
grpc
  // with a static header value
  .binaryHeader("key").value("value".toByteArray(UTF_8))
  // with a Gatling EL string header value
  .binaryHeader("key").valueEl("#{headerValue}")
  // with a function value
  .binaryHeader("key").value { session -> session.get("headerValue") }    
grpc
  // with a static header value
  .binaryHeader("key")("value".getBytes(UTF_8))
  // with a Gatling EL string header value
  .binaryHeader("key")("#{headerValue}")
  // with a function value
  .binaryHeader("key")(session => session("headerValue").as[Array[Byte]])
binaryHeaders

Shortcut for multiple headers with the default binary marshaller as a map of multiple key and pairs, i.e. io.grpc.Metadata#BINARY_BYTE_MARSHALLER:

     
grpc.binaryHeaders(Map.of("key", "value".getBytes(UTF_8)));
grpc.binaryHeaders(
  mapOf("key" to "value".toByteArray(UTF_8))
)
grpc.binaryHeaders(
  Map("key" -> "value".getBytes(UTF_8))
)

Add a single header with a custom key.

     
var key = Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER);
grpc
  // with a static header value
  .header(key).value("value")
  // with a Gatling EL string header value
  .header(key).valueEl("#{headerValue}")
  // with a function value
  .header(key).value(session -> session.get("headerValue"));
val key = Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER)
grpc
  // with a static header value
  .header(key).value("value")
  // with a Gatling EL string header value
  .header(key).valueEl("#{headerValue}")
  // with a function value
  .header(key).value { session -> session.get("headerValue") }
val key = Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER)
grpc
  // with a static header value
  .header[String](key)("value")
  // with a Gatling EL string header value
  .header[String](key)("#{headerValue}")
  // with a function value
  .header(key)(session => session("headerValue").as[String])

Loading balancing

When the name resolver returns a list of several service IP addresses, you probably want to configure a load balancing policy. The policy is responsible for maintaining connections to the services and picking a connection to use each time a request is sent.

useCustomLoadBalancingPolicy

Use a custom load balancing by name:

     
grpc.useCustomLoadBalancingPolicy("pick_first");
grpc.useCustomLoadBalancingPolicy("pick_first")
grpc.useCustomLoadBalancingPolicy("pick_first")

Or with JSON configuration:

     
grpc.useCustomLoadBalancingPolicy("pick_first", "{}");
grpc.useCustomLoadBalancingPolicy("pick_first", "{}")
grpc.useCustomLoadBalancingPolicy("pick_first", "{}")

Check the gRPC documentation for more details.

usePickFirstLoadBalancingPolicy

This policy actually does no load balancing but just tries each address it gets from the name resolver and uses the first one it can connect to:

     
grpc.usePickFirstLoadBalancingPolicy();
grpc.usePickFirstLoadBalancingPolicy()
grpc.usePickFirstLoadBalancingPolicy
usePickRandomLoadBalancingPolicy

Randomly pick an address from the name resolver:

     
grpc.usePickRandomLoadBalancingPolicy();
grpc.usePickRandomLoadBalancingPolicy()
grpc.usePickRandomLoadBalancingPolicy

This load balancing policy is bundled with Gatling gRPC but not a standard of gRPC.

useRoundRobinLoadBalancingPolicy

Round-robin load balancing over the addresses returned by the name resolver:

     
grpc.useRoundRobinLoadBalancingPolicy();
grpc.useRoundRobinLoadBalancingPolicy()
grpc.useRoundRobinLoadBalancingPolicy

Edit this page on GitHub