/xk6-client-tracing

A k6 extension for testing distributed tracing backends

Primary LanguageGo

xk6-client-tracing

⚠️ In Development

This project is in development and changes a lot between commits. Use at your own risk.

This extension provides k6 with the required functionality required to load test distributed tracing backends.

Usage

Generating traces and sending them to an agent or backend requires two things: a client and a trace generator. Generators have a method called traces() that can be used to generate traces. The client provides a method push() which receives the generated traces as first parameter and sends them to the configured collector.

Creating a client requires a client configuration:

const config = {
    endpoint: "localhost:4317",
    exporter: tracing.EXPORTER_OTLP,
};
let client = new tracing.Client(config);

The configuration is an object with the following schema:

{
    // The endpoint to which the traces are sent in the form of <hostname>:<port>
    endpoint: string,
    // The exporter protocol used for sending the traces: tracing.EXPORTER_OTLP or tracing.EXPORTER_JAEGER
    exporter: string,
    // Whether insecure connections are allowed (optional, default: false)
    insecure: bool,
    // Credentials used for authentication
    authentication: { user: string, password: string },
    // Additional headers sent by the client
    headers: { string : string }
}

There are two different types of generators which are described in the following sections.

Parameterized trace generator

This generator creates traces consisting of completely randomized spans. The spans contain a configurable number of random attributes with randomly assigned values. The main purpose of this generator is to create a large amount of spans with few lines of code.

An example can be found in ./examples/param.

Templated trace generator

This generator creates realistically looking and traces that contain spans with span name, span kind, and attributes. The trace is generated from a template configurations that describes how each should be generated.

The following listing creates a generator that creates traces with a single span:

const template = {
    spans: [
        {service: "article-service", name: "get-articles", attributes: {"http.method": "GET"}}
    ]
};
let gen = new tracing.TemplatedGenerator(template);
client.push(gen.traces());

The generated span will have the name get-articles. The generator will further assign a span kind as well as some commonly used attributes. There will also be a corresponding resource span with the respective service.name attribute.

The template has the following schema:

{
    // The defaults can be used to configure parameters that are applied to all spans (optional)
    defaults: {
        // Fixed attributes that are added to every generated span (optional)
        attributes: { string : any },
        // attributeSemantics can be set in order to generate attributes that follow a certain OpenTelemetry 
        // semantic convention. For example tracing.SEMANTICS_HTTP (optional)
        attributeSemantics: string,
        // Parameters to configure the creation of random attributes. If missing, no random attributes
        // are added to the spans (optional)
        randomAttributes: { 
            // The number of random attributes to generate
            count: int,
            // The number of distinct values to generate for each attribute (optional, default: 50)
            cardinality: int
        }
    },
    // Templates for the individual spans
    spans: [
        {
            // Is used to set the service.name attribute of the corresponding resource span
            service: string,
            // The name of the span. If empty, the name will be randomly generated (optional)
            name: string,
            // The index of the parent span in `spans`. The index must be smaller than the
            // own index. If empty, the parent is the span with the position directly before 
            // this span in `spans` (optional)
            parentIdx: int,
            // The interval for the generated span duration. If missing, a random duration is 
            // generated that is shorter than the duration of the parent span (optional)
            duration: { min: int, max: int },
            // Fixed attributes that are added to this (optional)
            attributes: { string : any },
            // attributeSemantics can be set in order to generate attributes that follow a certain OpenTelemetry 
            // semantic convention. For example tracing.SEMANTICS_HTTP (optional)
            attributeSemantics: string,
            // Parameters to configure the creation of random attributes. If missing, no random attributes
            // are added to the span (optional)
            randomAttributes: {
                // The number of random attributes to generate
                count: int,
                // The number of distinct values to generate for each attribute (optional, default: 50)
                cardinality: int
            }
        },
        ...
    ] 
}

An example with a templated generator can be found in ./examples/template.

Getting started

To start using the k6 tracing extension, ensure you have the following prerequisites installed:

  • Docker
  • docker-compose
  • make

Build docker image

The docker image is compiled using a multi-stage Docker build and does not require further dependencies. To start the build process run:

make docker

After the command completed successfully the image grafana/xk6-client-tracing:latest is available.

Run docker-compose example

Note: before running the docker-compose example, make sure to complete the docker image build step above!

To run the example cd into the directory examples/param and run:

docker-compose up -d

In the example k6-tracing uses the script param.js to generate spans and sends them to the otel-collector. The generated spans can be observed by inspecting the collector's logs:

docker-compose logs -f otel-collector

The example uses the OTLP gRPC exporter. If you want to use Jaeger gRPC, you can change param.js and use the following settings:

const client = new tracing.Client({
    endpoint: "otel-collector:14250",
    exporter: "jaeger",
    insecure: true,
});

Note: HTTP exporters aren't supported (yet)

Build locally

Building the extension locally has additional prerequisites:

Furthermore, the build also requires xk6 to compile k6 with the bundled tracing extension. Run the following command to install xk6:

go install go.k6.io/xk6/cmd/xk6@latest

To build binary run:

make build

The build step produces the k6-tracing binary. To test the binary you first need to change the endpoint in the client configuration to:

const client = new tracing.Client({
    endpoint: "localhost:4317",
    exporter: "otlp",
    insecure: true,
});

Once you've your new binary and configuration ready, you can run a local OTEL collector:

docker run --rm -p 13133:13133 -p 14250:14250 -p 14268:14268 \
      -p 55678-55679:55678-55679 -p 4317:4317 -p 9411:9411 \
      -v "${PWD}/examples/shared/collector-config.yaml":/collector-config.yaml \
      --name otelcol otel/opentelemetry-collector \
      --config collector-config.yaml

Once that's done, you can run a test like:

./k6-tracing run examples/basic/param.js

And see the generated spans in the OTEL collector logs!

Using the extension with Grafana Cloud

You can do that, by using the OTLP exporter and setting the required auth credentials:

const client = new tracing.Client({
    endpoint: "you-tempo-endpoint:443"
    exporter: "otlp",
    insecure: false,
    authentication: {
        user: "tenant-id",
        password: "api-token"
    }
});