├── .github
└── workflows
│ └── lint.yml
├── .gitignore
├── .golangci.yml
├── Dockerfile
├── LICENSE
├── README.md
├── Taskfile.yml
├── assets
└── xk6-opentelemetry-logo.png
├── attributes.go
├── collector_logs.go
├── collector_metrics.go
├── collector_trace.go
├── examples
├── test-logs.js
├── test-metrics-histogram.js
├── test-metrics.js
└── test-traces.js
├── generator.go
├── go.mod
├── go.sum
├── logs.go
├── metric_gauge.go
├── metric_histogram.go
├── metric_sum.go
├── metrics.go
├── module.go
├── resource.go
├── testing
├── docker-compose.yml
└── otel-collector.yaml
├── trace.go
└── trace_id_gen.go
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: lint
2 | on:
3 | push:
4 | tags:
5 | - v*
6 | branches:
7 | - main
8 | pull_request:
9 | permissions:
10 | contents: read
11 | pull-requests: read
12 | jobs:
13 | golangci:
14 | name: lint
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/setup-go@v3
18 | with:
19 | go-version: '1.22'
20 | - uses: actions/checkout@v3
21 | - name: golangci-lint
22 | uses: golangci/golangci-lint-action@v3
23 | with:
24 | version: v1.56.2
25 | only-new-issues: true
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | k6
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters:
2 | enable-all: true
3 | disable:
4 | - exhaustruct
5 | - nosnakecase
6 | - maligned # deprecated
7 | - golint # deprecated
8 | - ifshort # deprecated
9 | - structcheck # deprecated
10 | - varcheck # deprecated
11 | - exhaustivestruct # deprecated
12 | - interfacer # deprecated
13 | - scopelint # deprecated
14 | - deadcode # deprecated
15 | - rowserrcheck # generics
16 | - structcheck # generics
17 | - wastedassign # generics
18 | - gofumpt # generics
19 |
20 | issues:
21 | exclude-rules:
22 | - path: module\.go
23 | linters:
24 | - gochecknoinits
25 | - ireturn
26 | - text: "G404:"
27 | linters:
28 | - gosec
29 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.22 AS build
2 | WORKDIR /go/src/github.com/thmshmm/xk6-opentelemetry
3 | COPY . .
4 | RUN go install go.k6.io/xk6/cmd/xk6@latest
5 | RUN xk6 build --with xk6-opentelemetry=. --with github.com/mostafa/xk6-kafka
6 |
7 | FROM alpine:latest as base
8 | RUN apk add --no-cache ca-certificates && \
9 | addgroup -S app && adduser -S -G app app
10 |
11 | FROM scratch
12 | COPY --from=base /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
13 | COPY --from=base /etc/passwd /etc/passwd
14 | COPY --from=build /go/src/github.com/thmshmm/xk6-opentelemetry/k6 /k6
15 | USER app
16 | ENTRYPOINT ["/k6"]
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2023 Thomas Hamm
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
xk6-opentelemetry
2 |
3 | The xk6-opentelemetry project is a [k6 extension](https://k6.io/docs/extensions/guides/what-are-k6-extensions/) that enables k6 users to generate random [OpenTelemetry signals](https://opentelemetry.io/docs/reference/specification/glossary/#signals) (metrics, logs, traces) for testing purposes.
4 |
5 | Check the [examples](./examples/) directory which contains some scripts to get started.
6 |
7 | ## Features
8 |
9 | - Generate
10 | - ExportLogsServiceRequest
11 | - ExportMetricsServiceRequest (Types: gauge, sum, histogram)
12 | - ExportTraceServiceRequest
13 | - Set resource attributes
14 |
15 | ## Usage
16 |
17 | ### Docker
18 |
19 | The easiest way to get started is to build the provided Docker image and run k6 scripts inside the container.
20 |
21 | Build the image:
22 | ```
23 | docker build -t xk6-opentelemetry .
24 | ```
25 |
26 | Run a local k6 script:
27 | ```
28 | docker run --rm -i xk6-opentelemetry run - .js
72 | ```
73 |
74 | Run all examples:
75 | ```
76 | task run-examples
77 | ```
78 |
--------------------------------------------------------------------------------
/Taskfile.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | tasks:
4 | build-k6:
5 | cmds:
6 | - xk6 build --with xk6-opentelemetry=. --with github.com/mostafa/xk6-kafka
7 |
8 | build-docker:
9 | cmds:
10 | - docker build -t xk6-opentelemetry .
11 |
12 | lint:
13 | cmds:
14 | - golangci-lint run
15 |
16 | testing-up:
17 | dir: ./testing
18 | cmds:
19 | - docker compose -p xk6-opentelemetry up -d
20 |
21 | testing-down:
22 | dir: ./testing
23 | cmds:
24 | - docker compose -p xk6-opentelemetry down
25 |
26 | run-examples:
27 | cmds:
28 | - ./k6 run examples/test-logs.js
29 | - ./k6 run examples/test-metrics.js
30 | - ./k6 run examples/test-metrics-histogram.js
31 | - ./k6 run examples/test-traces.js
32 |
--------------------------------------------------------------------------------
/assets/xk6-opentelemetry-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thmshmm/xk6-opentelemetry/5694b6443907d093da38094977a47dd4eb9bd25f/assets/xk6-opentelemetry-logo.png
--------------------------------------------------------------------------------
/attributes.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
5 | )
6 |
7 | const unsupportedTypeValue = "unsupported_type"
8 |
9 | func ToAttributes(attrs map[string]interface{}) []*commonpb.KeyValue {
10 | attributes := make([]*commonpb.KeyValue, 0)
11 |
12 | for k, v := range attrs {
13 | attributes = append(attributes, &commonpb.KeyValue{
14 | Key: k,
15 | Value: ToAnyValue(v),
16 | })
17 | }
18 |
19 | return attributes
20 | }
21 |
22 | func ToAnyValue(attr interface{}) *commonpb.AnyValue {
23 | var value *commonpb.AnyValue
24 |
25 | switch attrValue := attr.(type) {
26 | case string:
27 | value = &commonpb.AnyValue{
28 | Value: &commonpb.AnyValue_StringValue{
29 | StringValue: attrValue,
30 | },
31 | }
32 | case int64:
33 | value = &commonpb.AnyValue{
34 | Value: &commonpb.AnyValue_IntValue{
35 | IntValue: attrValue,
36 | },
37 | }
38 | case int:
39 | value = &commonpb.AnyValue{
40 | Value: &commonpb.AnyValue_IntValue{
41 | IntValue: int64(attrValue),
42 | },
43 | }
44 | case float64:
45 | value = &commonpb.AnyValue{
46 | Value: &commonpb.AnyValue_DoubleValue{
47 | DoubleValue: attrValue,
48 | },
49 | }
50 | case bool:
51 | value = &commonpb.AnyValue{
52 | Value: &commonpb.AnyValue_BoolValue{
53 | BoolValue: attrValue,
54 | },
55 | }
56 | default:
57 | value = &commonpb.AnyValue{
58 | Value: &commonpb.AnyValue_StringValue{
59 | StringValue: unsupportedTypeValue,
60 | },
61 | }
62 | }
63 |
64 | return value
65 | }
66 |
--------------------------------------------------------------------------------
/collector_logs.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | collogspb "go.opentelemetry.io/proto/otlp/collector/logs/v1"
5 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
6 | logspb "go.opentelemetry.io/proto/otlp/logs/v1"
7 | )
8 |
9 | func ExportLogsServiceRequest(
10 | resourceAttrs []*commonpb.KeyValue,
11 | config LogConfig,
12 | ) *collogspb.ExportLogsServiceRequest {
13 | return &collogspb.ExportLogsServiceRequest{
14 | ResourceLogs: []*logspb.ResourceLogs{
15 | ResourceLogs(resourceAttrs, config),
16 | },
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/collector_metrics.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | colmetricspb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
5 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
6 | metricspb "go.opentelemetry.io/proto/otlp/metrics/v1"
7 | )
8 |
9 | func ExportMetricsServiceRequest(
10 | resourceAttrs []*commonpb.KeyValue,
11 | config MetricConfig,
12 | ) *colmetricspb.ExportMetricsServiceRequest {
13 | return &colmetricspb.ExportMetricsServiceRequest{
14 | ResourceMetrics: []*metricspb.ResourceMetrics{
15 | ResourceMetrics(resourceAttrs, config),
16 | },
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/collector_trace.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
5 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
6 | tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
7 | )
8 |
9 | func ExportTraceServiceRequest(
10 | resourceAttrs []*commonpb.KeyValue,
11 | config TraceConfig,
12 | ) *coltracepb.ExportTraceServiceRequest {
13 | return &coltracepb.ExportTraceServiceRequest{
14 | ResourceSpans: []*tracepb.ResourceSpans{
15 | ResourceSpans(resourceAttrs, config),
16 | },
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/test-logs.js:
--------------------------------------------------------------------------------
1 | import generator from 'k6/x/opentelemetry';
2 | import {
3 | Writer,
4 | SchemaRegistry,
5 | SCHEMA_TYPE_BYTES,
6 | } from 'k6/x/kafka';
7 | import {
8 | randomItem,
9 | randomIntBetween,
10 | } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';
11 |
12 | const brokers = ["localhost:9092"]
13 | const topic = "otel_logs"
14 | const schemaRegistry = new SchemaRegistry();
15 | const producer = new Writer({
16 | brokers: brokers,
17 | topic: topic,
18 | });
19 |
20 | const logBodies = [
21 | "this is a log msg",
22 | "this is another log msg"
23 | ]
24 |
25 | export default function () {
26 | var messages = []
27 |
28 | const resourceAttributes = new Map()
29 | resourceAttributes.set("pod", "test-abc")
30 | resourceAttributes.set("instance", 1)
31 | resourceAttributes.set("something", 1.23)
32 | resourceAttributes.set("isProd", true)
33 |
34 | // optionally set static attributes for the resource
35 | generator.setStaticResourceAttributes(resourceAttributes)
36 |
37 | for (let idx = 0; idx < 10; idx++) {
38 | messages[idx] = {
39 | value: schemaRegistry.serialize({
40 | data: Array.from(generator.exportLogsServiceRequest({
41 | // "attributes": logAttributes, // optionally create log specific attribute map
42 | "data": {
43 | "body": randomItem(logBodies),
44 | "severity": randomIntBetween(0, 24) // severity numbers from 0 to 24
45 | },
46 | })),
47 | schemaType: SCHEMA_TYPE_BYTES,
48 | })
49 | }
50 | }
51 |
52 | producer.produce({ messages: messages })
53 | }
54 |
--------------------------------------------------------------------------------
/examples/test-metrics-histogram.js:
--------------------------------------------------------------------------------
1 | import generator from 'k6/x/opentelemetry';
2 | import {
3 | Writer,
4 | SchemaRegistry,
5 | SCHEMA_TYPE_BYTES,
6 | } from 'k6/x/kafka';
7 | import {
8 | randomIntBetween,
9 | } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';
10 |
11 | const brokers = ["localhost:9092"]
12 | const topic = "otel_metrics"
13 | const schemaRegistry = new SchemaRegistry();
14 | const producer = new Writer({
15 | brokers: brokers,
16 | topic: topic,
17 | });
18 |
19 | // Example of having one cumulative histogram with 10 values after one execution.
20 | export default function () {
21 | var messages = []
22 | var count = 0
23 | var sum = 0.0
24 | var min = 100.0
25 | var max = 0.0
26 | var bucketCounts = [0, 0, 0] // (-inf,10], (10,50], (50,+inf]
27 |
28 | for (let idx = 0; idx < 10; idx++) {
29 | var recordedValue = randomIntBetween(0, 100)
30 |
31 | var bucketIdx = getBucketIdx(recordedValue)
32 | bucketCounts[bucketIdx] = bucketCounts[bucketIdx] + 1
33 |
34 | sum += recordedValue
35 | min = Math.min(min, recordedValue)
36 | max = Math.max(max, recordedValue)
37 |
38 | messages[idx] = {
39 | value: schemaRegistry.serialize({
40 | data: Array.from(generator.exportMetricsServiceRequest({
41 | "type": "histogram",
42 | "name": "hist_metric1",
43 | "unit": "ms",
44 | "data": {
45 | "aggregationTemporality": "cumulative",
46 | "count": ++count,
47 | "sum": sum,
48 | "min": min,
49 | "max": max,
50 | "explicitBounds": [10, 50],
51 | "bucketCounts": bucketCounts,
52 | },
53 | })),
54 | schemaType: SCHEMA_TYPE_BYTES,
55 | })
56 | }
57 | }
58 |
59 | producer.produce({ messages: messages })
60 | }
61 |
62 | function getBucketIdx(value) {
63 | if (value <= 10) {
64 | return 0
65 | } else if (value > 10 && value <= 50) {
66 | return 1
67 | }
68 |
69 | return 2 // value > 50
70 | }
71 |
--------------------------------------------------------------------------------
/examples/test-metrics.js:
--------------------------------------------------------------------------------
1 | import generator from 'k6/x/opentelemetry';
2 | import {
3 | Writer,
4 | SchemaRegistry,
5 | SCHEMA_TYPE_BYTES,
6 | } from 'k6/x/kafka';
7 | import {
8 | randomItem,
9 | randomIntBetween,
10 | } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';
11 |
12 | const brokers = ["localhost:9092"]
13 | const topic = "otel_metrics"
14 | const schemaRegistry = new SchemaRegistry();
15 | const producer = new Writer({
16 | brokers: brokers,
17 | topic: topic,
18 | });
19 |
20 | const metricNames = [
21 | "metric1",
22 | "metric2"
23 | ]
24 |
25 | const metricAttributes = new Map()
26 | metricAttributes.set("operation", "create")
27 |
28 | export default function () {
29 | var messages = []
30 |
31 | const resourceAttributes = new Map()
32 | resourceAttributes.set("pod", "test-abc")
33 |
34 | // optionally set static attributes for the resource
35 | generator.setStaticResourceAttributes(resourceAttributes)
36 |
37 | for (let idx = 0; idx < 5; idx++) {
38 | messages[idx] = {
39 | value: schemaRegistry.serialize({
40 | data: Array.from(generator.exportMetricsServiceRequest({
41 | "type": "gauge",
42 | "name": "gauge_" + randomItem(metricNames),
43 | "attributes": metricAttributes,
44 | "data": {
45 | "value": randomIntBetween(10, 100)
46 | },
47 | })),
48 | schemaType: SCHEMA_TYPE_BYTES,
49 | })
50 | }
51 | }
52 |
53 | for (let idx = 0; idx < 5; idx++) {
54 | messages[idx] = {
55 | value: schemaRegistry.serialize({
56 | data: Array.from(generator.exportMetricsServiceRequest({
57 | "type": "sum",
58 | "name": "sum_" + randomItem(metricNames),
59 | "attributes": metricAttributes,
60 | "data": {
61 | "value": randomIntBetween(10, 100),
62 | "isMonotonic": false, // true or false
63 | "aggregationTemporality": "delta" // delta or cumulative
64 | },
65 | })),
66 | schemaType: SCHEMA_TYPE_BYTES,
67 | })
68 | }
69 | }
70 |
71 | producer.produce({ messages: messages })
72 | }
73 |
--------------------------------------------------------------------------------
/examples/test-traces.js:
--------------------------------------------------------------------------------
1 | import generator from 'k6/x/opentelemetry';
2 | import {
3 | Writer,
4 | SchemaRegistry,
5 | SCHEMA_TYPE_BYTES,
6 | } from 'k6/x/kafka';
7 |
8 | const brokers = ["localhost:9092"]
9 | const topic = "otel_traces"
10 | const schemaRegistry = new SchemaRegistry();
11 | const producer = new Writer({
12 | brokers: brokers,
13 | topic: topic,
14 | });
15 |
16 | export default function () {
17 | var messages = []
18 |
19 | const resourceAttributes = new Map()
20 | resourceAttributes.set("pod", "test-abc")
21 |
22 | // make sure to set a service name for correct visualization in Grafana Tempo
23 | resourceAttributes.set("service.name", "service-1")
24 |
25 | // optionally set static attributes for the resource
26 | generator.setStaticResourceAttributes(resourceAttributes)
27 |
28 | for (let idx = 0; idx < 10; idx++) {
29 | var timeNow = generator.timeNowUnixNano()
30 |
31 | var traceId = generator.newTraceID()
32 |
33 | var span1Id = generator.newSpanID()
34 | var span1 = {
35 | "traceId": traceId,
36 | "spanId": span1Id,
37 | // root span has no parendSpanId
38 | "name": "say-hello",
39 | // "attributes": spanAttributes, // optionally create span specific attribute map
40 | "startTimeUnixNano": timeNow - 1e8, // 100ms earlier
41 | "endTimeUnixNano": timeNow,
42 | }
43 |
44 | var span2Id = generator.newSpanID()
45 | var span2 = {
46 | "traceId": traceId,
47 | "spanId": span2Id,
48 | "parentSpanId": span1Id,
49 | "name": "random-name",
50 | "kind": "client",
51 | "startTimeUnixNano": timeNow - 9e7,
52 | "endTimeUnixNano": timeNow - 4e7,
53 | }
54 |
55 | var span3Id = generator.newSpanID()
56 | var span3 = {
57 | "traceId": traceId,
58 | "spanId": span3Id,
59 | "parentSpanId": span1Id,
60 | "name": "enrich",
61 | "startTimeUnixNano": timeNow - 3e7,
62 | "endTimeUnixNano": timeNow - 1e7,
63 | }
64 |
65 | messages[idx] = {
66 | value: schemaRegistry.serialize({
67 | data: Array.from(generator.exportTraceServiceRequest({
68 | "data": [
69 | span1,
70 | span2,
71 | span3,
72 | ],
73 | })),
74 | schemaType: SCHEMA_TYPE_BYTES,
75 | })
76 | }
77 | }
78 |
79 | producer.produce({ messages: messages })
80 | }
81 |
--------------------------------------------------------------------------------
/generator.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "log/slog"
5 | "math/rand"
6 | "time"
7 |
8 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
9 | "google.golang.org/protobuf/proto"
10 | )
11 |
12 | type Generator struct {
13 | *IDGenerator
14 | staticResourceAttributes []*commonpb.KeyValue
15 | }
16 |
17 | // NewGenerator creates a Generator instance which can create supported OpenTelemetry signals.
18 | func NewGenerator() *Generator {
19 | return &Generator{
20 | IDGenerator: &IDGenerator{
21 | randSource: rand.New(rand.NewSource(time.Now().UnixNano())),
22 | },
23 | }
24 | }
25 |
26 | func (g *Generator) SetStaticResourceAttributes(attrs map[string]interface{}) {
27 | g.staticResourceAttributes = ToAttributes(attrs)
28 | }
29 |
30 | func (g *Generator) ExportLogsServiceRequest(config LogConfig) []byte {
31 | request := ExportLogsServiceRequest(g.staticResourceAttributes, config)
32 |
33 | data, err := proto.Marshal(request)
34 | if err != nil {
35 | slog.Error("Failed to marshal ExportLogsServiceRequest", "error", err)
36 |
37 | return nil
38 | }
39 |
40 | return data
41 | }
42 |
43 | func (g *Generator) ExportMetricsServiceRequest(config MetricConfig) []byte {
44 | request := ExportMetricsServiceRequest(g.staticResourceAttributes, config)
45 |
46 | data, err := proto.Marshal(request)
47 | if err != nil {
48 | slog.Error("Failed to marshal ExportMetricsServiceRequest", "error", err)
49 |
50 | return nil
51 | }
52 |
53 | return data
54 | }
55 |
56 | func (g *Generator) ExportTraceServiceRequest(config TraceConfig) []byte {
57 | request := ExportTraceServiceRequest(g.staticResourceAttributes, config)
58 |
59 | data, err := proto.Marshal(request)
60 | if err != nil {
61 | slog.Error("Failed to marshal ExportTraceServiceRequest", "error", err)
62 |
63 | return nil
64 | }
65 |
66 | return data
67 | }
68 |
69 | func (g *Generator) TimeNowUnixNano() int64 {
70 | return time.Now().UnixNano()
71 | }
72 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/thmshmm/xk6-opentelemetry
2 |
3 | go 1.20
4 |
5 | require (
6 | go.k6.io/k6 v0.51.1-0.20240610082146-1f01a9bc2365
7 | go.opentelemetry.io/proto/otlp v1.1.0
8 | google.golang.org/protobuf v1.33.0
9 | )
10 |
11 | require (
12 | github.com/cenkalti/backoff/v4 v4.2.1 // indirect
13 | github.com/dlclark/regexp2 v1.9.0 // indirect
14 | github.com/dop251/goja v0.0.0-20240516125602-ccbae20bcec2 // indirect
15 | github.com/evanw/esbuild v0.21.2 // indirect
16 | github.com/fatih/color v1.16.0 // indirect
17 | github.com/go-logr/logr v1.4.1 // indirect
18 | github.com/go-logr/stdr v1.2.2 // indirect
19 | github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
20 | github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 // indirect
21 | github.com/grafana/sobek v0.0.0-20240607083612-4f0cd64f4e78 // indirect
22 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
23 | github.com/josharian/intern v1.0.0 // indirect
24 | github.com/mailru/easyjson v0.7.7 // indirect
25 | github.com/mattn/go-colorable v0.1.13 // indirect
26 | github.com/mattn/go-isatty v0.0.20 // indirect
27 | github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd // indirect
28 | github.com/onsi/ginkgo v1.16.5 // indirect
29 | github.com/onsi/gomega v1.27.2 // indirect
30 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect
31 | github.com/sirupsen/logrus v1.9.3 // indirect
32 | github.com/spf13/afero v1.1.2 // indirect
33 | go.opentelemetry.io/otel v1.24.0 // indirect
34 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
35 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
36 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
37 | go.opentelemetry.io/otel/metric v1.24.0 // indirect
38 | go.opentelemetry.io/otel/sdk v1.24.0 // indirect
39 | go.opentelemetry.io/otel/trace v1.24.0 // indirect
40 | golang.org/x/net v0.26.0 // indirect
41 | golang.org/x/sys v0.21.0 // indirect
42 | golang.org/x/text v0.16.0 // indirect
43 | golang.org/x/time v0.5.0 // indirect
44 | google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
45 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
46 | google.golang.org/grpc v1.63.2 // indirect
47 | gopkg.in/guregu/null.v3 v3.3.0 // indirect
48 | )
49 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
2 | github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
3 | github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 | github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
8 | github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
9 | github.com/dop251/goja v0.0.0-20240516125602-ccbae20bcec2 h1:OFTHt+yJDo/uaIKMGjEKzc3DGhrpQZoqvMUIloZv6ZY=
10 | github.com/dop251/goja v0.0.0-20240516125602-ccbae20bcec2/go.mod h1:o31y53rb/qiIAONF7w3FHJZRqqP3fzHUr1HqanthByw=
11 | github.com/evanw/esbuild v0.21.2 h1:CLplcGi794CfHLVmUbvVfTMKkykm+nyIHU8SU60KUTA=
12 | github.com/evanw/esbuild v0.21.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
13 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
14 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
15 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
16 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
17 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
18 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
19 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
20 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
21 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
22 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
23 | github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
24 | github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
25 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
26 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
27 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
28 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
29 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
30 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
31 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
32 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
33 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
34 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
35 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
36 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
37 | github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 h1:ZgoomqkdjGbQ3+qQXCkvYMCDvGDNg2k5JJDjjdTB6jY=
38 | github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
39 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
40 | github.com/grafana/sobek v0.0.0-20240607083612-4f0cd64f4e78 h1:rVCZdB+13G+aQoGm3CBVaDGl0uxZxfjvQgEJy4IeHTA=
41 | github.com/grafana/sobek v0.0.0-20240607083612-4f0cd64f4e78/go.mod h1:6ZH0b0iOxyigeTh+/IlGoL0Hd3lVXA94xoXf0ldNgCM=
42 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
43 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
44 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
45 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
46 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
47 | github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
48 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
49 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
50 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
51 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
52 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
53 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
54 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
55 | github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa h1:lx8ZnNPwjkXSzOROz0cg69RlErRXs+L3eDkggASWKLo=
56 | github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd h1:AC3N94irbx2kWGA8f/2Ks7EQl2LxKIRQYuT9IJDwgiI=
57 | github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd/go.mod h1:9vRHVuLCjoFfE3GT06X0spdOAO+Zzo4AMjdIwUHBvAk=
58 | github.com/mstoykov/envconfig v1.5.0 h1:E2FgWf73BQt0ddgn7aoITkQHmgwAcHup1s//MsS5/f8=
59 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
60 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
61 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
62 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
63 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
64 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
65 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
66 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
67 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
68 | github.com/onsi/gomega v1.27.2 h1:SKU0CXeKE/WVgIV1T61kSa3+IRE8Ekrv9rdXDwwTqnY=
69 | github.com/onsi/gomega v1.27.2/go.mod h1:5mR3phAHpkAVIDkHEUBY6HGVsU+cpcEscrGPB4oPlZI=
70 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
71 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
72 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e h1:zWKUYT07mGmVBH+9UgnHXd/ekCK99C8EbDSAt5qsjXE=
73 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
74 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
75 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
76 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
77 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
78 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
79 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
80 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
81 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
82 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
83 | go.k6.io/k6 v0.51.1-0.20240610082146-1f01a9bc2365 h1:ZXlJs5hXt1hbY4k3jHVJS8xrgypgTZAwbMBVH1EMCgY=
84 | go.k6.io/k6 v0.51.1-0.20240610082146-1f01a9bc2365/go.mod h1:LJKmFwUODAYoxitsJ3Xk+wsyVJDpyQiLyJAVn+oGyVQ=
85 | go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
86 | go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
87 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
88 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
89 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
90 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
91 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
92 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
93 | go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
94 | go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
95 | go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
96 | go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
97 | go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
98 | go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
99 | go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
100 | go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
101 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
102 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
103 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
104 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
105 | golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
106 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
107 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
108 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
109 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
110 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
111 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
112 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
113 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
114 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
115 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
116 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
117 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
118 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
119 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
120 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
121 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
122 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
123 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
124 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
125 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
126 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
127 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
128 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
129 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
130 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
131 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
132 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
133 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
134 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
135 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
136 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
137 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
138 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
139 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
140 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
141 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
142 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
143 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
144 | google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
145 | google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
146 | google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
147 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
148 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
149 | google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
150 | google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
151 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
152 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
153 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
154 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
155 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
156 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
157 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
158 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
159 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
160 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
161 | gopkg.in/guregu/null.v3 v3.3.0 h1:8j3ggqq+NgKt/O7mbFVUFKUMWN+l1AmT5jQmJ6nPh2c=
162 | gopkg.in/guregu/null.v3 v3.3.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y=
163 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
164 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
165 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
166 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
167 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
168 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
169 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
170 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
171 |
--------------------------------------------------------------------------------
/logs.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "time"
5 |
6 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
7 | logspb "go.opentelemetry.io/proto/otlp/logs/v1"
8 | )
9 |
10 | type LogConfig struct {
11 | Attributes map[string]interface{} `js:"attributes"`
12 | Data LogData `js:"data"`
13 | }
14 |
15 | type LogData struct {
16 | Body string `js:"body"`
17 | Severity int32 `js:"severity"`
18 | }
19 |
20 | func ResourceLogs(resourceAttrs []*commonpb.KeyValue, config LogConfig) *logspb.ResourceLogs {
21 | return &logspb.ResourceLogs{
22 | Resource: resource(resourceAttrs),
23 | ScopeLogs: []*logspb.ScopeLogs{
24 | {
25 | LogRecords: []*logspb.LogRecord{
26 | logRecord(ToAttributes(config.Attributes), config.Data),
27 | },
28 | },
29 | },
30 | }
31 | }
32 |
33 | func logRecord(attrs []*commonpb.KeyValue, data LogData) *logspb.LogRecord {
34 | return &logspb.LogRecord{
35 | Attributes: attrs,
36 | TimeUnixNano: uint64(time.Now().UnixNano()),
37 | SeverityNumber: logspb.SeverityNumber(data.Severity),
38 | Body: &commonpb.AnyValue{
39 | Value: &commonpb.AnyValue_StringValue{
40 | StringValue: data.Body,
41 | },
42 | },
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/metric_gauge.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "time"
7 |
8 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
9 | metricspb "go.opentelemetry.io/proto/otlp/metrics/v1"
10 | )
11 |
12 | type GaugeData struct {
13 | Value int64 `json:"value"`
14 | }
15 |
16 | func parseGaugeData(rawData map[string]interface{}) (*GaugeData, error) {
17 | dataBytes, err := json.Marshal(rawData)
18 | if err != nil {
19 | return nil, fmt.Errorf("failed to process provided gauge data: %w", err)
20 | }
21 |
22 | var data GaugeData
23 | err = json.Unmarshal(dataBytes, &data)
24 | if err != nil {
25 | return nil, fmt.Errorf("failed to parse JSON gauge data: %w", err)
26 | }
27 |
28 | return &data, nil
29 | }
30 |
31 | func gauge(attrs []*commonpb.KeyValue, data *GaugeData) *metricspb.Metric_Gauge {
32 | return &metricspb.Metric_Gauge{
33 | Gauge: &metricspb.Gauge{
34 | DataPoints: []*metricspb.NumberDataPoint{
35 | {
36 | Attributes: attrs,
37 | TimeUnixNano: uint64(time.Now().UnixNano()),
38 | Value: &metricspb.NumberDataPoint_AsInt{
39 | AsInt: data.Value,
40 | },
41 | },
42 | },
43 | },
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/metric_histogram.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "time"
7 |
8 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
9 | metricspb "go.opentelemetry.io/proto/otlp/metrics/v1"
10 | )
11 |
12 | type HistogramData struct {
13 | AggregationTemporality string `json:"aggregationTemporality"`
14 | Count uint64 `json:"count"`
15 | Sum float64 `json:"sum"`
16 | Min float64 `json:"min"`
17 | Max float64 `json:"max"`
18 | ExplicitBounds []float64 `json:"explicitBounds"`
19 | BucketCounts []uint64 `json:"bucketCounts"`
20 | }
21 |
22 | func parseHistogramData(rawData map[string]interface{}) (*HistogramData, error) {
23 | dataBytes, err := json.Marshal(rawData)
24 | if err != nil {
25 | return nil, fmt.Errorf("failed to process provided histogram data: %w", err)
26 | }
27 |
28 | var data HistogramData
29 | err = json.Unmarshal(dataBytes, &data)
30 | if err != nil {
31 | return nil, fmt.Errorf("failed to parse JSON histogram data: %w", err)
32 | }
33 |
34 | return &data, nil
35 | }
36 |
37 | func histogram(attrs []*commonpb.KeyValue, data *HistogramData) *metricspb.Metric_Histogram {
38 | return &metricspb.Metric_Histogram{
39 | Histogram: &metricspb.Histogram{
40 | AggregationTemporality: getAggregationTemporality(data.AggregationTemporality),
41 | DataPoints: []*metricspb.HistogramDataPoint{
42 | {
43 | Attributes: attrs,
44 | TimeUnixNano: uint64(time.Now().UnixNano()),
45 | Count: data.Count,
46 | Sum: &data.Sum,
47 | Min: &data.Min,
48 | Max: &data.Max,
49 | ExplicitBounds: data.ExplicitBounds,
50 | BucketCounts: data.BucketCounts,
51 | },
52 | },
53 | },
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/metric_sum.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "time"
7 |
8 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
9 | metricspb "go.opentelemetry.io/proto/otlp/metrics/v1"
10 | )
11 |
12 | type SumData struct {
13 | Value int64 `json:"value"`
14 | IsMonotonic bool `json:"isMonotonic"`
15 | AggregationTemporality string `json:"aggregationTemporality"`
16 | }
17 |
18 | func parseSumData(rawData map[string]interface{}) (*SumData, error) {
19 | dataBytes, err := json.Marshal(rawData)
20 | if err != nil {
21 | return nil, fmt.Errorf("failed to process provided sum data: %w", err)
22 | }
23 |
24 | var data SumData
25 | err = json.Unmarshal(dataBytes, &data)
26 | if err != nil {
27 | return nil, fmt.Errorf("failed to parse JSON sum data: %w", err)
28 | }
29 |
30 | return &data, nil
31 | }
32 |
33 | func sum(attrs []*commonpb.KeyValue, data *SumData) *metricspb.Metric_Sum {
34 | return &metricspb.Metric_Sum{
35 | Sum: &metricspb.Sum{
36 | IsMonotonic: data.IsMonotonic,
37 | AggregationTemporality: getAggregationTemporality(data.AggregationTemporality),
38 | DataPoints: []*metricspb.NumberDataPoint{
39 | {
40 | Attributes: attrs,
41 | TimeUnixNano: uint64(time.Now().UnixNano()),
42 | Value: &metricspb.NumberDataPoint_AsInt{
43 | AsInt: data.Value,
44 | },
45 | },
46 | },
47 | },
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/metrics.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "log/slog"
5 | "strings"
6 |
7 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
8 | metricspb "go.opentelemetry.io/proto/otlp/metrics/v1"
9 | )
10 |
11 | type MetricConfig struct {
12 | Type string `js:"type"`
13 | Name string `js:"name"`
14 | Unit string `js:"unit"`
15 | Attributes map[string]interface{} `js:"attributes"`
16 | Data map[string]interface{} `js:"data"`
17 | }
18 |
19 | func ResourceMetrics(resourceAttrs []*commonpb.KeyValue, config MetricConfig) *metricspb.ResourceMetrics {
20 | metric := &metricspb.Metric{
21 | Name: config.Name,
22 | Unit: config.Unit,
23 | }
24 |
25 | attrs := ToAttributes(config.Attributes)
26 |
27 | switch config.Type {
28 | case "gauge":
29 | data, err := parseGaugeData(config.Data)
30 | if err != nil {
31 | slog.Error("Failed to parse gauge data", "error", err)
32 |
33 | return nil
34 | }
35 |
36 | metric.Data = gauge(attrs, data)
37 | case "sum":
38 | data, err := parseSumData(config.Data)
39 | if err != nil {
40 | slog.Error("Failed to parse sum data", "error", err)
41 |
42 | return nil
43 | }
44 |
45 | metric.Data = sum(attrs, data)
46 | case "histogram":
47 | data, err := parseHistogramData(config.Data)
48 | if err != nil {
49 | slog.Error("Failed to parse histogram data", "error", err)
50 |
51 | return nil
52 | }
53 |
54 | metric.Data = histogram(attrs, data)
55 | default:
56 | slog.Error("Unimplemented metric type %q, use one of [gauge, sum]", "type", config.Type)
57 |
58 | return nil
59 | }
60 |
61 | return &metricspb.ResourceMetrics{
62 | Resource: resource(resourceAttrs),
63 | ScopeMetrics: []*metricspb.ScopeMetrics{
64 | {
65 | Metrics: []*metricspb.Metric{
66 | metric,
67 | },
68 | },
69 | },
70 | }
71 | }
72 |
73 | func getAggregationTemporality(temporality string) metricspb.AggregationTemporality {
74 | switch strings.ToLower(temporality) {
75 | case "delta":
76 | return metricspb.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA
77 | case "cumulative":
78 | return metricspb.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE
79 | }
80 |
81 | return metricspb.AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED
82 | }
83 |
--------------------------------------------------------------------------------
/module.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "go.k6.io/k6/js/modules"
5 | )
6 |
7 | func init() {
8 | modules.Register("k6/x/opentelemetry", New())
9 | }
10 |
11 | type (
12 | RootModule struct{}
13 | ModuleInstance struct {
14 | generator *Generator
15 | }
16 | )
17 |
18 | var (
19 | _ modules.Instance = &ModuleInstance{}
20 | _ modules.Module = &RootModule{}
21 | )
22 |
23 | // New returns a pointer to a new RootModule instance.
24 | func New() *RootModule {
25 | return &RootModule{}
26 | }
27 |
28 | // NewModuleInstance implements the modules.Module interface returning a new instance for each VU.
29 | func (*RootModule) NewModuleInstance(_ modules.VU) modules.Instance {
30 | return &ModuleInstance{
31 | generator: NewGenerator(),
32 | }
33 | }
34 |
35 | // Exports implements the modules.Instance interface and returns the exported types for the JS module.
36 | func (mi *ModuleInstance) Exports() modules.Exports {
37 | return modules.Exports{
38 | Default: mi.generator,
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/resource.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
5 | resourcepb "go.opentelemetry.io/proto/otlp/resource/v1"
6 | )
7 |
8 | func resource(attrs []*commonpb.KeyValue) *resourcepb.Resource {
9 | return &resourcepb.Resource{
10 | Attributes: attrs,
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/testing/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | redpanda:
5 | image: vectorized/redpanda:v23.3.7
6 | command: redpanda start --overprovisioned --smp 1 --memory 1G --reserve-memory 0M --node-id 0 --check=false --kafka-addr INSIDE://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092 --advertise-kafka-addr INSIDE://broker:29092,OUTSIDE://localhost:9092
7 | container_name: broker
8 | ports:
9 | - "9092:9092"
10 | collector:
11 | image: otel/opentelemetry-collector-contrib:0.96.0
12 | command:
13 | - --config
14 | - /etc/otel/config.yaml
15 | volumes:
16 | - ./otel-collector.yaml/:/etc/otel/config.yaml
17 | restart: always
18 |
--------------------------------------------------------------------------------
/testing/otel-collector.yaml:
--------------------------------------------------------------------------------
1 | receivers:
2 | kafka/logs:
3 | brokers:
4 | - broker:29092
5 | group_id: collector-logs
6 | topic: otel_logs
7 | kafka/metrics:
8 | brokers:
9 | - broker:29092
10 | group_id: collector-metrics
11 | topic: otel_metrics
12 | kafka/traces:
13 | brokers:
14 | - broker:29092
15 | group_id: collector-traces
16 | topic: otel_traces
17 |
18 | exporters:
19 | logging:
20 | verbosity: detailed
21 |
22 | processors:
23 | batch:
24 |
25 | service:
26 | pipelines:
27 | logs:
28 | receivers:
29 | - kafka/logs
30 | processors:
31 | - batch
32 | exporters:
33 | - logging
34 | metrics:
35 | receivers:
36 | - kafka/metrics
37 | processors:
38 | - batch
39 | exporters:
40 | - logging
41 | traces:
42 | receivers:
43 | - kafka/traces
44 | processors:
45 | - batch
46 | exporters:
47 | - logging
48 |
49 | telemetry:
50 | logs:
51 | level: "info"
52 |
--------------------------------------------------------------------------------
/trace.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "encoding/hex"
5 | "log/slog"
6 | "strings"
7 |
8 | commonpb "go.opentelemetry.io/proto/otlp/common/v1"
9 | tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
10 | )
11 |
12 | type TraceConfig struct {
13 | Data []SpanData `js:"data"`
14 | }
15 |
16 | type SpanData struct {
17 | TraceID string `js:"traceId"`
18 | SpanID string `js:"spanId"`
19 | ParentSpanID string `js:"parentSpanId"`
20 | Name string `js:"name"`
21 | Attributes map[string]interface{} `js:"attributes"`
22 | Kind string `js:"kind"`
23 | StartTimeUnixNano uint64 `js:"startTimeUnixNano"`
24 | EndTimeUnixNano uint64 `js:"endTimeUnixNano"`
25 | }
26 |
27 | func ResourceSpans(resourceAttrs []*commonpb.KeyValue, config TraceConfig) *tracepb.ResourceSpans {
28 | return &tracepb.ResourceSpans{
29 | Resource: resource(resourceAttrs),
30 | ScopeSpans: []*tracepb.ScopeSpans{
31 | {
32 | Spans: spans(config.Data),
33 | },
34 | },
35 | }
36 | }
37 |
38 | func spans(data []SpanData) []*tracepb.Span {
39 | spans := []*tracepb.Span{}
40 |
41 | for _, spanData := range data {
42 | spans = append(spans, span(spanData))
43 | }
44 |
45 | return spans
46 | }
47 |
48 | func span(data SpanData) *tracepb.Span {
49 | traceID, err := hex.DecodeString(data.TraceID)
50 | if err != nil {
51 | slog.Error("invalid trace id")
52 | }
53 |
54 | spanID, err := hex.DecodeString(data.SpanID)
55 | if err != nil {
56 | slog.Error("invalid span id")
57 | }
58 |
59 | parentSpanID, err := hex.DecodeString(data.ParentSpanID)
60 | if err != nil {
61 | slog.Error("invalid parent span id")
62 | }
63 |
64 | return &tracepb.Span{
65 | TraceId: traceID,
66 | SpanId: spanID,
67 | ParentSpanId: parentSpanID,
68 | Name: data.Name,
69 | Attributes: ToAttributes(data.Attributes),
70 | Kind: getSpanKind(data.Kind),
71 | StartTimeUnixNano: data.StartTimeUnixNano,
72 | EndTimeUnixNano: data.EndTimeUnixNano,
73 | }
74 | }
75 |
76 | func getSpanKind(kind string) tracepb.Span_SpanKind {
77 | switch strings.ToLower(kind) {
78 | case "client":
79 | return tracepb.Span_SPAN_KIND_CLIENT
80 | case "server":
81 | return tracepb.Span_SPAN_KIND_SERVER
82 | case "consumer":
83 | return tracepb.Span_SPAN_KIND_CONSUMER
84 | case "producer":
85 | return tracepb.Span_SPAN_KIND_PRODUCER
86 | }
87 |
88 | return tracepb.Span_SPAN_KIND_INTERNAL
89 | }
90 |
--------------------------------------------------------------------------------
/trace_id_gen.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "encoding/hex"
5 | "math/rand"
6 | "sync"
7 | )
8 |
9 | type IDGenerator struct {
10 | sync.Mutex
11 | randSource *rand.Rand
12 | }
13 |
14 | func (idGen *IDGenerator) NewTraceID() string {
15 | idGen.Lock()
16 | defer idGen.Unlock()
17 |
18 | var id [16]byte
19 |
20 | _, _ = idGen.randSource.Read(id[:])
21 |
22 | return hex.EncodeToString(id[:])
23 | }
24 |
25 | func (idGen *IDGenerator) NewSpanID() string {
26 | idGen.Lock()
27 | defer idGen.Unlock()
28 |
29 | var id [8]byte
30 |
31 | _, _ = idGen.randSource.Read(id[:])
32 |
33 | return hex.EncodeToString(id[:])
34 | }
35 |
--------------------------------------------------------------------------------