-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Align configuration with OpenTelemetry SDK, HTTP exporter #5
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,13 +5,36 @@ A work in progress k6 extension to output real-time test metrics in [OpenTelemet | |
> [!WARNING] | ||
> It's work in progress implementation and not ready for production use. | ||
|
||
Configuration options (currently environment variables only): | ||
## Configuration options | ||
|
||
Currently, environment variables only. It's worth to mention that the extension is using the [OpenTelemetry Go SDK](https://opentelemetry.io/docs/languages/go/getting-started/) that's why it's possible to use the configuration environment variables from the SDK. However, if the `K6_OTEL_*` environment variables are set, they will take precedence over the SDK configuration. | ||
|
||
### k6-specific configuration | ||
|
||
* `K6_OTEL_RECEIVER_TYPE` - OpenTelemetry receiver type, currently only `grpc` is supported. Default is `grpc`. | ||
* `K6_OTEL_RECEIVER_ENDPOINT` - OpenTelemetry receiver endpoint. Default is `localhost:4317`. | ||
* `K6_OTEL_METRIC_PREFIX` - Metric prefix. Default is empty. | ||
* `K6_OTEL_FLUSH_INTERVAL` - How frequently to flush metrics to the receiver from k6. Default is `1s`. | ||
* `K6_OTEL_PUSH_INTERVAL` - How frequently to push metrics to the receiver from k6. Default is `1s`. | ||
* `K6_OTEL_FLUSH_INTERVAL` - How frequently to flush metrics from k6 metrics engine. Default is `1s`. | ||
|
||
### OpenTelemetry-specific configuration | ||
|
||
* `K6_OTEL_EXPORT_INTERVAL` - configures the intervening time between metrics exports. Default is `1s`. | ||
* `K6_OTEL_EXPORTER_TYPE` - metric exporter type. Default is `grpc`. | ||
|
||
#### GRPC exporter | ||
|
||
* `K6_OTEL_GRPC_EXPORTER_INSECURE` - disables client transport security for the gRPC exporter. | ||
* `K6_OTEL_GRPC_EXPORTER_ENDPOINT` - configures the gRPC exporter endpoint. Default is `localhost:4317`. | ||
|
||
> [!TIP] | ||
> Also, you can use [OpenTelemetry SDK configuration environment variables](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/[email protected]). | ||
|
||
#### HTTP exporter | ||
|
||
* `K6_OTEL_HTTP_EXPORTER_INSECURE` - disables client transport security for the HTTP exporter. | ||
* `K6_OTEL_HTTP_EXPORTER_ENDPOINT` - configures the HTTP exporter endpoint. Default is `localhost:4318`. | ||
* `K6_OTEL_HTTP_EXPORTER_URL_PATH` - configures the HTTP exporter path. Default is `/v1/metrics`. | ||
|
||
> [!TIP] | ||
> Also, you can use [OpenTelemetry SDK configuration environment variables](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/[email protected]). | ||
|
||
## Build | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,82 +5,185 @@ import ( | |
"fmt" | ||
"time" | ||
|
||
k6Const "go.k6.io/k6/lib/consts" | ||
"go.k6.io/k6/output" | ||
"gopkg.in/guregu/null.v3" | ||
) | ||
|
||
const ( | ||
grpcReceiverType = "grpc" | ||
// grpcExporterType GRPC exporter type | ||
grpcExporterType = "grpc" | ||
// httpExporterType HTTP exporter type | ||
httpExporterType = "http" | ||
) | ||
|
||
// Config is the config for the template collector | ||
type Config struct { | ||
// ServiceName is the name of the service to use for the metrics | ||
// export, if not set it will use "k6" | ||
ServiceName string | ||
// ServiceVersion is the version of the service to use for the metrics | ||
// export, if not set it will use k6's library version | ||
ServiceVersion string | ||
// MetricPrefix is the prefix to use for the metrics | ||
MetricPrefix string | ||
// ReceiverType is the type of the receiver to use | ||
ReceiverType string | ||
// GRPCReceiverEndpoint is the endpoint of the gRPC receiver | ||
GRPCReceiverEndpoint string | ||
// PushInterval is the interval at which to push metrics to the receiver | ||
PushInterval time.Duration | ||
// FlushInterval is the interval at which to flush metrics from the k6 | ||
FlushInterval time.Duration | ||
|
||
// ExporterType sets the type of OpenTelemetry Exporter to use | ||
ExporterType string | ||
// ExportInterval configures the intervening time between metrics exports | ||
ExportInterval time.Duration | ||
|
||
// HTTPExporterInsecure disables client transport security for the Exporter's HTTP | ||
// connection. | ||
HTTPExporterInsecure null.Bool | ||
// HTTPExporterEndpoint sets the target endpoint the OpenTelemetry Exporter | ||
// will connect to. | ||
HTTPExporterEndpoint string | ||
// HTTPExporterURLPath sets the target URL path the OpenTelemetry Exporter | ||
HTTPExporterURLPath string | ||
|
||
// GRPCExporterEndpoint sets the target endpoint the OpenTelemetry Exporter | ||
// will connect to. | ||
GRPCExporterEndpoint string | ||
// GRPCExporterInsecure disables client transport security for the Exporter's gRPC | ||
// connection. | ||
GRPCExporterInsecure null.Bool | ||
} | ||
|
||
// NewConfig creates and validates a new config | ||
func NewConfig(p output.Params) (Config, error) { | ||
cfg := Config{ | ||
MetricPrefix: "", | ||
ReceiverType: grpcReceiverType, | ||
GRPCReceiverEndpoint: "localhost:4317", | ||
PushInterval: 1 * time.Second, | ||
FlushInterval: 1 * time.Second, | ||
ServiceName: "k6", | ||
ServiceVersion: k6Const.Version, | ||
MetricPrefix: "", | ||
ExporterType: grpcExporterType, | ||
|
||
HTTPExporterInsecure: null.BoolFrom(false), | ||
HTTPExporterEndpoint: "localhost:4318", | ||
HTTPExporterURLPath: "/v1/metrics", | ||
|
||
GRPCExporterInsecure: null.BoolFrom(false), | ||
GRPCExporterEndpoint: "localhost:4317", | ||
|
||
ExportInterval: 1 * time.Second, | ||
FlushInterval: 1 * time.Second, | ||
} | ||
|
||
var err error | ||
for k, v := range p.Environment { | ||
switch k { | ||
case "K6_OTEL_PUSH_INTERVAL": | ||
cfg.PushInterval, err = time.ParseDuration(v) | ||
if err != nil { | ||
return cfg, fmt.Errorf("error parsing environment variable 'K6_OTEL_PUSH_INTERVAL': %w", err) | ||
} | ||
case "K6_OTEL_SERVICE_NAME": | ||
cfg.ServiceName = v | ||
case "K6_OTEL_SERVICE_VERSION": | ||
cfg.ServiceVersion = v | ||
Comment on lines
75
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not certain if jut using envconfig.Process as all the other outputs will not be better especially before we move the whole configuration over to not using envconfig. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, implemented as part of: #11 |
||
case "K6_OTEL_METRIC_PREFIX": | ||
cfg.MetricPrefix = v | ||
case "K6_OTEL_EXPORT_INTERVAL": | ||
cfg.ExportInterval, err = time.ParseDuration(v) | ||
if err != nil { | ||
return cfg, fmt.Errorf("error parsing environment variable 'K6_OTEL_EXPORT_INTERVAL': %w", err) | ||
} | ||
case "K6_OTEL_FLUSH_INTERVAL": | ||
cfg.FlushInterval, err = time.ParseDuration(v) | ||
if err != nil { | ||
return cfg, fmt.Errorf("error parsing environment variable 'K6_OTEL_FLUSH_INTERVAL': %w", err) | ||
} | ||
case "K6_OTEL_RECEIVER_TYPE": | ||
cfg.ReceiverType = v | ||
case "K6_OTEL_GRPC_RECEIVER_ENDPOINT": | ||
cfg.GRPCReceiverEndpoint = v | ||
case "K6_OTEL_EXPORTER_TYPE": | ||
cfg.ExporterType = v | ||
case "K6_OTEL_GRPC_EXPORTER_ENDPOINT": | ||
cfg.GRPCExporterEndpoint = v | ||
case "K6_OTEL_HTTP_EXPORTER_ENDPOINT": | ||
cfg.HTTPExporterEndpoint = v | ||
case "K6_OTEL_HTTP_EXPORTER_URL_PATH": | ||
cfg.HTTPExporterURLPath = v | ||
case "K6_OTEL_HTTP_EXPORTER_INSECURE": | ||
cfg.HTTPExporterInsecure, err = parseBool(k, v) | ||
if err != nil { | ||
return cfg, err | ||
} | ||
case "K6_OTEL_GRPC_EXPORTER_INSECURE": | ||
cfg.GRPCExporterInsecure, err = parseBool(k, v) | ||
if err != nil { | ||
return cfg, err | ||
} | ||
} | ||
} | ||
|
||
// TDOO: consolidated config | ||
|
||
if err = cfg.Validate(); err != nil { | ||
return cfg, fmt.Errorf("error validating config: %w", err) | ||
return cfg, fmt.Errorf("error validating OpenTelemetry output config: %w", err) | ||
} | ||
|
||
return cfg, nil | ||
} | ||
|
||
func parseBool(k, v string) (null.Bool, error) { | ||
bv := null.NewBool(false, false) | ||
|
||
err := bv.UnmarshalText([]byte(v)) | ||
if err != nil { | ||
return bv, fmt.Errorf("error parsing %q environment variable: %w", k, err) | ||
} | ||
|
||
return bv, nil | ||
} | ||
|
||
// Validate validates the config | ||
func (c Config) Validate() error { | ||
if c.ReceiverType != grpcReceiverType { | ||
return fmt.Errorf("unsupported receiver type %q, currently only %q supported", c.ReceiverType, grpcReceiverType) | ||
if c.ServiceName == "" { | ||
return errors.New("providing service name is required") | ||
} | ||
|
||
if c.GRPCReceiverEndpoint == "" { | ||
return errors.New("gRPC receiver endpoint is required") | ||
if c.ServiceVersion == "" { | ||
return errors.New("providing service version is required") | ||
} | ||
|
||
if c.ExporterType != grpcExporterType && c.ExporterType != httpExporterType { | ||
return fmt.Errorf( | ||
"unsupported exporter type %q, currently only %q and %q are supported", | ||
c.ExporterType, | ||
grpcExporterType, | ||
httpExporterType, | ||
) | ||
} | ||
|
||
if c.ExporterType == grpcExporterType { | ||
if c.GRPCExporterEndpoint == "" { | ||
return errors.New("gRPC exporter endpoint is required") | ||
} | ||
} | ||
|
||
if c.ExporterType == httpExporterType { | ||
if c.HTTPExporterEndpoint == "" { | ||
return errors.New("HTTP exporter endpoint is required") | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// String returns a string representation of the config | ||
func (c Config) String() string { | ||
return fmt.Sprintf("%s, %s", c.ReceiverType, c.GRPCReceiverEndpoint) | ||
var endpoint string | ||
exporter := c.ExporterType | ||
|
||
if c.ExporterType == httpExporterType { | ||
endpoint = "http" | ||
if !c.HTTPExporterInsecure.Bool { | ||
endpoint += "s" | ||
} | ||
|
||
endpoint += "://" + c.HTTPExporterEndpoint + c.HTTPExporterURLPath | ||
} else { | ||
endpoint = c.GRPCExporterEndpoint | ||
|
||
if c.GRPCExporterInsecure.Bool { | ||
exporter += " (insecure)" | ||
} | ||
} | ||
|
||
return fmt.Sprintf("%s, %s", exporter, endpoint) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: In general it is better idea to always use
null
types for everything as that lets you know if you should use a default value or if somebody just managed to match it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented as part of: #11