Skip to content

Commit

Permalink
Merge pull request #4562 from grafana/feat/opentelemetry-output
Browse files Browse the repository at this point in the history
Move opentelemetry's ouput code to the k6 core
  • Loading branch information
olegbespalov authored Feb 20, 2025
2 parents c5fb807 + 72b7884 commit 1017671
Show file tree
Hide file tree
Showing 13 changed files with 546 additions and 673 deletions.
11 changes: 5 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ require (
github.com/gorilla/websocket v1.5.3
github.com/grafana/sobek v0.0.0-20250219104821-ed22af7a8d6c
github.com/grafana/xk6-dashboard v0.7.5
github.com/grafana/xk6-output-opentelemetry v0.3.0
github.com/grafana/xk6-redis v0.3.3
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/influxdata/influxdb1-client v0.0.0-20190402204710-8ff2fc3824fc
Expand All @@ -41,11 +40,16 @@ require (
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.18.0
go.opentelemetry.io/otel v1.32.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0
go.opentelemetry.io/otel/metric v1.32.0
go.opentelemetry.io/otel/sdk v1.32.0
go.opentelemetry.io/otel/sdk/metric v1.32.0
go.opentelemetry.io/otel/trace v1.32.0
go.opentelemetry.io/proto/otlp v1.3.1
go.uber.org/goleak v1.3.0
golang.org/x/crypto v0.33.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20250210163342-e47973b1c108
Expand Down Expand Up @@ -88,11 +92,6 @@ require (
github.com/redis/go-redis/v9 v9.0.5 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ github.com/grafana/sobek v0.0.0-20250219104821-ed22af7a8d6c h1:AnjwFNvLSgko/nVMG
github.com/grafana/sobek v0.0.0-20250219104821-ed22af7a8d6c/go.mod h1:FmcutBFPLiGgroH42I4/HBahv7GxVjODcVWFTw1ISes=
github.com/grafana/xk6-dashboard v0.7.5 h1:TcILyffT/Ea/XD7xG1jMA5lwtusOPRbEQsQDHmO30Mk=
github.com/grafana/xk6-dashboard v0.7.5/go.mod h1:Y75F8xmgCraKT+pBzFH6me9AyH5PkXD+Bxo1dm6Il/M=
github.com/grafana/xk6-output-opentelemetry v0.3.0 h1:dmclGBFtFVRJijqLncpu2dKVIIvx1GS3y6sQGg4Khl8=
github.com/grafana/xk6-output-opentelemetry v0.3.0/go.mod h1:5uUg0J2dxrw+DkC9fCsfiKv2jTpccn7nz6vGmRUr4f8=
github.com/grafana/xk6-redis v0.3.3 h1:9QiS4OUjYMzvriGzbqrhCn9i/kENmCkibZE+xCJSdPk=
github.com/grafana/xk6-redis v0.3.3/go.mod h1:YL1qfBZXW4o2aAF6/gRa/N9oGoumUg7cJzzCHLxoCOw=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import (
"go.k6.io/k6/internal/output/cloud"
"go.k6.io/k6/internal/output/influxdb"
"go.k6.io/k6/internal/output/json"
"go.k6.io/k6/internal/output/opentelemetry"
"go.k6.io/k6/internal/output/prometheusrw/remotewrite"
"go.k6.io/k6/lib"
"go.k6.io/k6/output"
"go.k6.io/k6/output/csv"

"github.com/grafana/xk6-dashboard/dashboard"
"github.com/grafana/xk6-output-opentelemetry/pkg/opentelemetry"
)

// builtinOutput marks the available builtin outputs.
Expand Down
File renamed without changes.
File renamed without changes.
204 changes: 204 additions & 0 deletions internal/output/opentelemetry/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package opentelemetry

import (
"encoding/json"
"testing"
"time"

"github.com/stretchr/testify/require"
k6Const "go.k6.io/k6/lib/consts"
"go.k6.io/k6/lib/types"
"gopkg.in/guregu/null.v3"
)

func TestConfig(t *testing.T) {
t.Parallel()
// TODO: add more cases
testCases := map[string]struct {
jsonRaw json.RawMessage
env map[string]string
arg string
expectedConfig Config
err string
}{
"default": {
expectedConfig: Config{
ServiceName: null.StringFrom("k6"),
ServiceVersion: null.StringFrom(k6Const.Version),
ExporterType: null.StringFrom(grpcExporterType),
HTTPExporterInsecure: null.NewBool(false, true),
HTTPExporterEndpoint: null.StringFrom("localhost:4318"),
HTTPExporterURLPath: null.StringFrom("/v1/metrics"),
GRPCExporterInsecure: null.NewBool(false, true),
GRPCExporterEndpoint: null.StringFrom("localhost:4317"),
ExportInterval: types.NullDurationFrom(10 * time.Second),
FlushInterval: types.NullDurationFrom(1 * time.Second),
},
},

"environment success merge": {
env: map[string]string{"K6_OTEL_GRPC_EXPORTER_ENDPOINT": "else", "K6_OTEL_EXPORT_INTERVAL": "4ms"},
expectedConfig: Config{
ServiceName: null.StringFrom("k6"),
ServiceVersion: null.StringFrom(k6Const.Version),
ExporterType: null.StringFrom(grpcExporterType),
HTTPExporterInsecure: null.NewBool(false, true),
HTTPExporterEndpoint: null.StringFrom("localhost:4318"),
HTTPExporterURLPath: null.StringFrom("/v1/metrics"),
GRPCExporterInsecure: null.NewBool(false, true),
GRPCExporterEndpoint: null.StringFrom("else"),
ExportInterval: types.NullDurationFrom(4 * time.Millisecond),
FlushInterval: types.NullDurationFrom(1 * time.Second),
},
},

"environment complete overwrite": {
env: map[string]string{
"K6_OTEL_SERVICE_NAME": "foo",
"K6_OTEL_SERVICE_VERSION": "v0.0.99",
"K6_OTEL_EXPORTER_TYPE": "http",
"K6_OTEL_EXPORT_INTERVAL": "4ms",
"K6_OTEL_HTTP_EXPORTER_INSECURE": "true",
"K6_OTEL_HTTP_EXPORTER_ENDPOINT": "localhost:5555",
"K6_OTEL_HTTP_EXPORTER_URL_PATH": "/foo/bar",
"K6_OTEL_GRPC_EXPORTER_INSECURE": "true",
"K6_OTEL_GRPC_EXPORTER_ENDPOINT": "else",
"K6_OTEL_FLUSH_INTERVAL": "13s",
"K6_OTEL_TLS_INSECURE_SKIP_VERIFY": "true",
"K6_OTEL_TLS_CERTIFICATE": "cert_path",
"K6_OTEL_TLS_CLIENT_CERTIFICATE": "client_cert_path",
"K6_OTEL_TLS_CLIENT_KEY": "client_key_path",
"K6_OTEL_HEADERS": "key1=value1,key2=value2",
},
expectedConfig: Config{
ServiceName: null.StringFrom("foo"),
ServiceVersion: null.StringFrom("v0.0.99"),
ExporterType: null.StringFrom(httpExporterType),
ExportInterval: types.NullDurationFrom(4 * time.Millisecond),
HTTPExporterInsecure: null.NewBool(true, true),
HTTPExporterEndpoint: null.StringFrom("localhost:5555"),
HTTPExporterURLPath: null.StringFrom("/foo/bar"),
GRPCExporterInsecure: null.NewBool(true, true),
GRPCExporterEndpoint: null.StringFrom("else"),
FlushInterval: types.NullDurationFrom(13 * time.Second),
TLSInsecureSkipVerify: null.NewBool(true, true),
TLSCertificate: null.StringFrom("cert_path"),
TLSClientCertificate: null.StringFrom("client_cert_path"),
TLSClientKey: null.StringFrom("client_key_path"),
Headers: null.StringFrom("key1=value1,key2=value2"),
},
},

"OTEL environment variables": {
env: map[string]string{
"OTEL_SERVICE_NAME": "otel-service",
},
expectedConfig: Config{
ServiceName: null.StringFrom("otel-service"),
ServiceVersion: null.StringFrom(k6Const.Version),
ExporterType: null.StringFrom(grpcExporterType),
HTTPExporterInsecure: null.NewBool(false, true),
HTTPExporterEndpoint: null.StringFrom("localhost:4318"),
HTTPExporterURLPath: null.StringFrom("/v1/metrics"),
GRPCExporterInsecure: null.NewBool(false, true),
GRPCExporterEndpoint: null.StringFrom("localhost:4317"),
ExportInterval: types.NullDurationFrom(10 * time.Second),
FlushInterval: types.NullDurationFrom(1 * time.Second),
},
},

"JSON complete overwrite": {
jsonRaw: json.RawMessage(
`{` +
`"serviceName":"bar",` +
`"serviceVersion":"v2.0.99",` +
`"exporterType":"http",` +
`"exportInterval":"15ms",` +
`"httpExporterInsecure":true,` +
`"httpExporterEndpoint":"localhost:5555",` +
`"httpExporterURLPath":"/foo/bar",` +
`"grpcExporterInsecure":true,` +
`"grpcExporterEndpoint":"else",` +
`"flushInterval":"13s",` +
`"tlsInsecureSkipVerify":true,` +
`"tlsCertificate":"cert_path",` +
`"tlsClientCertificate":"client_cert_path",` +
`"tlsClientKey":"client_key_path",` +
`"headers":"key1=value1,key2=value2"` +
`}`,
),
expectedConfig: Config{
ServiceName: null.StringFrom("bar"),
ServiceVersion: null.StringFrom("v2.0.99"),
ExporterType: null.StringFrom(httpExporterType),
ExportInterval: types.NullDurationFrom(15 * time.Millisecond),
HTTPExporterInsecure: null.NewBool(true, true),
HTTPExporterEndpoint: null.StringFrom("localhost:5555"),
HTTPExporterURLPath: null.StringFrom("/foo/bar"),
GRPCExporterInsecure: null.NewBool(true, true),
GRPCExporterEndpoint: null.StringFrom("else"),
FlushInterval: types.NullDurationFrom(13 * time.Second),
TLSInsecureSkipVerify: null.NewBool(true, true),
TLSCertificate: null.StringFrom("cert_path"),
TLSClientCertificate: null.StringFrom("client_cert_path"),
TLSClientKey: null.StringFrom("client_key_path"),
Headers: null.StringFrom("key1=value1,key2=value2"),
},
},

"JSON success merge": {
jsonRaw: json.RawMessage(`{"exporterType":"http","httpExporterEndpoint":"localhost:5566","httpExporterURLPath":"/lorem/ipsum", "exportInterval":"15ms"}`),
expectedConfig: Config{
ServiceName: null.StringFrom("k6"),
ServiceVersion: null.StringFrom(k6Const.Version),
ExporterType: null.StringFrom(httpExporterType),
HTTPExporterInsecure: null.NewBool(false, true),
HTTPExporterEndpoint: null.StringFrom("localhost:5566"),
HTTPExporterURLPath: null.StringFrom("/lorem/ipsum"),
GRPCExporterInsecure: null.NewBool(false, true), // default
GRPCExporterEndpoint: null.StringFrom("localhost:4317"), // default
ExportInterval: types.NullDurationFrom(15 * time.Millisecond),
FlushInterval: types.NullDurationFrom(1 * time.Second),
},
},
"no scheme in http exporter": {
jsonRaw: json.RawMessage(`{"exporterType":"http","httpExporterEndpoint":"http://localhost:5566","httpExporterURLPath":"/lorem/ipsum", "exportInterval":"15ms"}`),
err: `config: HTTP exporter endpoint must only be host and port, no scheme`,
},

"early error env": {
env: map[string]string{"K6_OTEL_GRPC_EXPORTER_ENDPOINT": "else", "K6_OTEL_EXPORT_INTERVAL": "4something"},
err: `time: unknown unit "something" in duration "4something"`,
},

"early error JSON": {
jsonRaw: json.RawMessage(`{"exportInterval":"4something"}`),
err: `time: unknown unit "something" in duration "4something"`,
},

"unsupported receiver type": {
env: map[string]string{"K6_OTEL_GRPC_EXPORTER_ENDPOINT": "else", "K6_OTEL_EXPORT_INTERVAL": "4m", "K6_OTEL_EXPORTER_TYPE": "socket"},
err: `error validating OpenTelemetry output config: unsupported exporter type "socket", currently only "grpc" and "http" are supported`,
},

"missing required": {
jsonRaw: json.RawMessage(`{"exporterType":"http","httpExporterEndpoint":"","httpExporterURLPath":"/lorem/ipsum"}`),
err: `HTTP exporter endpoint is required`,
},
}

for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
config, err := GetConsolidatedConfig(testCase.jsonRaw, testCase.env)
if testCase.err != "" {
require.Error(t, err)
require.Contains(t, err.Error(), testCase.err)
return
}
require.NoError(t, err)
require.Equal(t, testCase.expectedConfig, config)
})
}
}
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 1017671

Please sign in to comment.