Skip to content

gRPC load testing

gRPC is a high-performance RPC framework using Protocol Buffers over HTTP/2. Load-testing gRPC services requires a client that understands the binary framing and can serialize protobuf messages. In MaxoPerf, the best supported approach for gRPC load testing 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 are testing — k6 loads them at run time to understand 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();
}

The .proto file (product_service.proto) must be uploaded as a Test asset alongside 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 commit real private keys to the uploaded test asset — use MaxoPerf secrets for keys and pass them via __ENV.CLIENT_KEY instead.

Do:

  • Upload the .proto file as a Test asset — k6 needs it at run time to encode/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).
  • Use thresholds on grpc_req_duration in k6 options to automatically fail the run 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 status of grpc.StatusUnavailable under load indicates the server is shedding requests.