Skip to content

Commit b9e5c10

Browse files
committed
Refactoring and template trace generator (#5)
Refactored the code and introduce the concept of different trace generators. The existing functionality to generate traces is now provided by tracing.ParameterizedGenerator Implement a new generator that creates traces and spans from a configurable template with the goal to generate more realistiacally looking traces
1 parent f5d3c6a commit b9e5c10

16 files changed

+1546
-1051
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
k6
1+
k6
2+
k6-tracing

README.md

+124-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,125 @@
66
77
This extension provides k6 with the required functionality required to load test distributed tracing backends.
88

9+
## Usage
10+
11+
Generating traces and sending them to an agent or backend requires two things: a client and a trace generator.
12+
Generators have a method called `traces()` that can be used to generate traces.
13+
The client provides a method `push()` which receives the generated traces as first parameter and sends them to the configured collector.
14+
15+
Creating a client requires a client configuration:
16+
17+
```javascript
18+
const config = {
19+
endpoint: "localhost:4317",
20+
exporter: tracing.EXPORTER_OTLP,
21+
};
22+
let client = new tracing.Client(config);
23+
```
24+
25+
The configuration is an object with the following schema:
26+
27+
```javascript
28+
{
29+
// The endpoint to which the traces are sent in the form of <hostname>:<port>
30+
endpoint: string,
31+
// The exporter protocol used for sending the traces: tracing.EXPORTER_OTLP or tracing.EXPORTER_JAEGER
32+
exporter: string,
33+
// Whether insecure connections are allowed (optional, default: false)
34+
insecure: bool,
35+
// Credentials used for authentication
36+
authentication: { user: string, password: string },
37+
// Additional headers sent by the client
38+
headers: { string : string }
39+
}
40+
```
41+
42+
There are two different types of generators which are described in the following sections.
43+
44+
### Parameterized trace generator
45+
46+
This generator creates traces consisting of completely randomized spans.
47+
The spans contain a configurable number of random attributes with randomly assigned values.
48+
The main purpose of this generator is to create a large amount of spans with few lines of code.
49+
50+
An example can be found in [./examples/param](./examples/param).
51+
52+
### Templated trace generator
53+
54+
This generator creates realistically looking and traces that contain spans with span name, span kind, and attributes.
55+
The trace is generated from a template configurations that describes how each should be generated.
56+
57+
The following listing creates a generator that creates traces with a single span:
58+
59+
```javascript
60+
const template = {
61+
spans: [
62+
{service: "article-service", name: "get-articles", attributes: {"http.method": "GET"}}
63+
]
64+
};
65+
let gen = new tracing.TemplatedGenerator(template);
66+
client.push(gen.traces());
67+
```
68+
69+
The generated span will have the name `get-articles`.
70+
The generator will further assign a span kind as well as some commonly used attributes.
71+
There will also be a corresponding resource span with the respective `service.name` attribute.
72+
73+
The template has the following schema:
74+
75+
```javascript
76+
{
77+
// The defaults can be used to configure parameters that are applied to all spans (optional)
78+
defaults: {
79+
// Fixed attributes that are added to every generated span (optional)
80+
attributes: { string : any },
81+
// attributeSemantics can be set in order to generate attributes that follow a certain OpenTelemetry
82+
// semantic convention. For example tracing.SEMANTICS_HTTP (optional)
83+
attributeSemantics: string,
84+
// Parameters to configure the creation of random attributes. If missing, no random attributes
85+
// are added to the spans (optional)
86+
randomAttributes: {
87+
// The number of random attributes to generate
88+
count: int,
89+
// The number of distinct values to generate for each attribute (optional, default: 50)
90+
cardinality: int
91+
}
92+
},
93+
// Templates for the individual spans
94+
spans: [
95+
{
96+
// Is used to set the service.name attribute of the corresponding resource span
97+
service: string,
98+
// The name of the span. If empty, the name will be randomly generated (optional)
99+
name: string,
100+
// The index of the parent span in `spans`. The index must be smaller than the
101+
// own index. If empty, the parent is the span with the position directly before
102+
// this span in `spans` (optional)
103+
parentIdx: int,
104+
// The interval for the generated span duration. If missing, a random duration is
105+
// generated that is shorter than the duration of the parent span (optional)
106+
duration: { min: int, max: int },
107+
// Fixed attributes that are added to this (optional)
108+
attributes: { string : any },
109+
// attributeSemantics can be set in order to generate attributes that follow a certain OpenTelemetry
110+
// semantic convention. For example tracing.SEMANTICS_HTTP (optional)
111+
attributeSemantics: string,
112+
// Parameters to configure the creation of random attributes. If missing, no random attributes
113+
// are added to the span (optional)
114+
randomAttributes: {
115+
// The number of random attributes to generate
116+
count: int,
117+
// The number of distinct values to generate for each attribute (optional, default: 50)
118+
cardinality: int
119+
}
120+
},
121+
...
122+
]
123+
}
124+
```
125+
126+
An example with a templated generator can be found in [./examples/template](./examples/template).
127+
9128
## Getting started
10129

11130
To start using the k6 tracing extension, ensure you have the following prerequisites installed:
@@ -29,21 +148,21 @@ After the command completed successfully the image `grafana/xk6-client-tracing:l
29148

30149
> Note: before running the docker-compose example, make sure to complete the docker image build step above!
31150
32-
To run the example `cd` into the directory `examples/basic` and run:
151+
To run the example `cd` into the directory `examples/param` and run:
33152

34153
```shell
35154
docker-compose up -d
36155
```
37156

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

41160
```shell
42161
docker-compose logs -f otel-collector
43162
```
44163

45164
The example uses the OTLP gRPC exporter.
46-
If you want to use Jaeger gRPC, you can change `basic.js` and use the following settings:
165+
If you want to use Jaeger gRPC, you can change `param.js` and use the following settings:
47166

48167
```javascript
49168
const client = new tracing.Client({
@@ -75,7 +194,7 @@ make build
75194
```
76195

77196
The build step produces the `k6-tracing` binary.
78-
To test the binary you first need to change the endpoint in the client configuration in `examples/basic/basic.js`:
197+
To test the binary you first need to change the endpoint in the client configuration to:
79198

80199
```javascript
81200
const client = new tracing.Client({
@@ -96,7 +215,7 @@ docker run --rm -p 13133:13133 -p 14250:14250 -p 14268:14268 \
96215

97216
Once that's done, you can run a test like:
98217
```
99-
./k6-tracing run examples/basic/basic.js
218+
./k6-tracing run examples/basic/param.js
100219
```
101220

102221
And see the generated spans in the OTEL collector logs!

examples/basic/docker-compose.yml examples/param/docker-compose.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ services:
55
image: grafana/xk6-client-tracing:latest
66
command:
77
- run
8-
- /basic.js
8+
- /param.js
99
volumes:
10-
- ./basic.js:/basic.js:ro
10+
- ./param.js:/param.js:ro
1111
depends_on:
1212
- otel-collector
1313

examples/basic/basic.js examples/param/param.js

+6-12
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
import { sleep } from 'k6';
22
import tracing from 'k6/x/tracing';
33
import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
4-
import { SharedArray } from 'k6/data';
54

65
export let options = {
76
vus: 1,
87
duration: "5m",
98
};
109

11-
const traceIDs = new SharedArray('traceIDs', function () {
12-
let toret = [];
13-
for (let i = 0; i < 10; i++) {
14-
toret.push(tracing.generateRandomTraceID());
15-
}
16-
return toret;
17-
});
18-
1910
const client = new tracing.Client({
2011
endpoint: "otel-collector:4317",
21-
exporter: "otlp",
12+
exporter: tracing.EXPORTER_OTLP,
2213
insecure: true,
2314
});
2415

@@ -31,7 +22,6 @@ export default function () {
3122
pushSizeSpans += c;
3223

3324
t.push({
34-
id: traceIDs[Math.floor(Math.random() * traceIDs.length)],
3525
random_service_name: false,
3626
spans: {
3727
count: c,
@@ -43,7 +33,11 @@ export default function () {
4333
}
4434
});
4535
}
46-
client.push(t);
36+
37+
let gen = new tracing.ParameterizedGenerator(t)
38+
let traces = gen.traces()
39+
client.push(traces);
40+
4741
console.log(`Pushed ${pushSizeSpans} spans from ${pushSizeTraces} different traces. Here is a random traceID: ${t[Math.floor(Math.random() * t.length)].id}`);
4842
sleep(15);
4943
}

examples/template/docker-compose.yml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
version: "3"
2+
3+
services:
4+
k6-tracing:
5+
image: grafana/xk6-client-tracing:latest
6+
command:
7+
- run
8+
- /template.js
9+
volumes:
10+
- ./template.js:/template.js:ro
11+
depends_on:
12+
- otel-collector
13+
14+
otel-collector:
15+
image: otel/opentelemetry-collector:latest
16+
command:
17+
- --config=/collector-config.yaml
18+
volumes:
19+
- ../shared/collector-config.yaml:/collector-config.yaml:ro
20+
ports:
21+
- "13133:13133"
22+
- "14250:14250"
23+
- "14268:14268"
24+
- "55678-55679:55678-55679"
25+
- "4317:4317"
26+
- "9411:9411"

examples/template/template.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {sleep} from 'k6';
2+
import tracing from 'k6/x/tracing';
3+
4+
export const options = {
5+
vus: 1,
6+
duration: "5m",
7+
};
8+
9+
const client = new tracing.Client({
10+
endpoint: "otel-collector:4317",
11+
exporter: tracing.EXPORTER_OTLP,
12+
insecure: true,
13+
});
14+
15+
const traceDefaults = {
16+
attributeSemantics: tracing.SEMANTICS_HTTP,
17+
attributes: {"one": "three"},
18+
randomAttributes: {count: 2, cardinality: 5}
19+
}
20+
21+
const traceTemplates = [
22+
{
23+
defaults: traceDefaults,
24+
spans: [
25+
{service: "shop-backend", name: "list-articles", duration: {min: 200, max: 900}},
26+
{service: "shop-backend", name: "authenticate", duration: {min: 50, max: 100}},
27+
{service: "auth-service", name: "authenticate"},
28+
{service: "shop-backend", name: "fetch-articles", parentIdx: 0},
29+
{service: "article-service", name: "get-articles"},
30+
{service: "article-service", name: "select-articles", attributeSemantics: tracing.SEMANTICS_DB},
31+
{service: "postgres", name: "query-articles", attributeSemantics: tracing.SEMANTICS_DB, randomAttributes: {count: 5}},
32+
]
33+
},
34+
{
35+
defaults: traceDefaults,
36+
spans: [
37+
{service: "shop-backend", attributes: {"http.status_code": 403}},
38+
{service: "shop-backend", name: "authenticate"},
39+
{service: "auth-service", name: "authenticate", attributes: {"http.status_code": 403}},
40+
]
41+
},
42+
]
43+
44+
export default function () {
45+
traceTemplates.forEach(function (tmpl) {
46+
let gen = new tracing.TemplatedGenerator(tmpl)
47+
let traces = gen.traces()
48+
client.push(traces)
49+
});
50+
51+
sleep(5);
52+
}
53+
54+
export function teardown() {
55+
client.shutdown();
56+
}

0 commit comments

Comments
 (0)