Skip to content

gRPC load testing

gRPC is an RPC framework that sends Protocol Buffers over HTTP/2. To load-test it you need a client that understands the binary framing and can serialize protobuf messages. In MaxoPerf, the best-supported option is k6’s built-in k6/net/grpc module.

  • Read k6 scripts on Maxoperf. gRPC load testing uses k6 as the script engine.
  • You need the .proto file(s) for the service you test. k6 loads them at run time to read the method signatures.

k6 ships a k6/net/grpc module that lets you call gRPC unary and server-streaming methods:

import grpc from 'k6/net/grpc';
import { check, sleep } from 'k6';
const client = new grpc.Client();
// Load the proto file — upload it as a Test asset alongside this script
client.load(['./proto'], 'product_service.proto');
export const options = {
vus: 20,
duration: '3m',
thresholds: {
grpc_req_duration: ['p(95)<200'],
},
};
export default function () {
// Open connection (once per VU — k6 re-uses it across iterations)
client.connect('grpc.api.example.com:443', {
tls: true,
});
// Unary call
const response = client.invoke('product.v1.ProductService/GetProduct', {
product_id: 'widget-01',
});
check(response, {
'status OK': (r) => r && r.status === grpc.StatusOK,
'product name present': (r) => r.message && r.message.name !== '',
});
sleep(1);
client.close();
}

Upload the .proto file (product_service.proto) as a Test asset next to the k6 script. The client.load(['./proto'], 'filename.proto') call tells k6 where to find it relative to the script root.

If your proto imports other protos, upload them all as test assets and reference them via the import path array:

// Load multiple protos with a shared import path
client.load(
['./proto', './proto/google'], // directories to search for imports
'product_service.proto', // entry proto file
'google/protobuf/timestamp.proto' // imported by product_service.proto
);

Upload your files as:

  • product_service.proto → Test asset
  • google/protobuf/timestamp.proto → Test asset

Wrapping the k6 gRPC script in Taurus YAML

Section titled “Wrapping the k6 gRPC script in Taurus YAML”

To control concurrency and duration from a YAML without editing the script:

execution:
- executor: k6
concurrency: 30
ramp-up: 1m
hold-for: 10m
scenario: grpc-product-load
scenarios:
grpc-product-load:
script: grpc_product_test.js # uploaded as a Test asset

Upload the YAML as the Entrypoint and the k6 script + proto files as Test assets.

k6’s gRPC module also supports server-streaming calls:

const stream = client.stream('product.v1.ProductService/WatchProduct', {
product_id: 'widget-01',
});
stream.on('data', (message) => {
check(message, { 'update received': (m) => m.name !== '' });
});
stream.on('end', () => {
// stream closed by server
});
stream.end();

gRPC results appear in the standard MaxoPerf run-detail view. Key metrics from the k6 gRPC module:

k6 metricMaxoPerf display
grpc_req_durationLatency chart (p50, p95, p99)
grpc_req_failedError rate
grpc_requestsThroughput (calls per second)

Each distinct client.invoke(...) call appears as a separate label in the per-endpoint breakdown if you add a name tag:

const response = client.invoke(
'product.v1.ProductService/GetProduct',
{ product_id: 'widget-01' },
{ tags: { name: 'GetProduct' } } // name appears in MaxoPerf per-endpoint panel
);

For services requiring mutual TLS:

client.connect('grpc.api.example.com:443', {
tls: true,
// For mTLS:
// tlsCert: open('client.crt', 'r'),
// tlsKey: open('client.key', 'r'),
// tlsCACert: open('ca.crt', 'r'),
});

Upload the certificate and key files as Test assets. Never put real private keys in an uploaded test asset. Store keys as MaxoPerf secrets and pass them through __ENV.CLIENT_KEY instead.

Do:

  • Upload the .proto file as a Test asset. k6 needs it at run time to encode and decode protobuf messages.
  • Close the gRPC connection with client.close() at the end of each VU iteration if you re-open it per iteration (or manage connection lifetime deliberately for connection-pool testing).
  • Set thresholds on grpc_req_duration in k6 options so the run fails when latency exceeds your SLO.

Don’t:

  • Inline proto definitions as strings in the k6 script. Upload the .proto file as a test asset and use client.load().
  • Forget to handle gRPC status codes in check(). A grpc.StatusUnavailable status under load means the server is shedding requests.